@prestack/thread 0.1.3 → 0.1.5
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 +563 -0
- package/dist/core/actor.d.ts +1 -1
- package/dist/core/actor.js +5 -4
- package/dist/core/actor.ts +5 -4
- package/dist/core/attach.d.ts +0 -7
- package/dist/core/attach.js +38 -67
- package/dist/core/attach.ts +34 -80
- package/dist/core/chain.js +13 -17
- package/dist/core/chain.ts +11 -15
- package/dist/core/condition.d.ts +4 -4
- package/dist/core/condition.js +2 -2
- package/dist/core/descend.d.ts +4 -2
- package/dist/core/descend.js +91 -23
- package/dist/core/descend.ts +90 -21
- package/dist/core/errors/catalogue.d.ts +52 -0
- package/dist/{operations/errors/errors.js → core/errors/catalogue.js} +1 -1
- package/dist/{operations/errors/errors.ts → core/errors/catalogue.ts} +1 -1
- package/dist/{operations/errors/catalog.d.ts → core/errors/error.d.ts} +2 -3
- package/dist/{operations/errors/catalog.js → core/errors/error.js} +1 -16
- package/dist/{operations/errors/catalog.ts → core/errors/error.ts} +3 -19
- package/dist/core/errors/namespaces.d.ts +3 -0
- package/dist/core/errors/namespaces.js +15 -0
- package/dist/core/errors/namespaces.ts +19 -0
- package/dist/core/finding.js +2 -2
- package/dist/core/finding.ts +1 -2
- package/dist/core/init.d.ts +1 -1
- package/dist/core/init.js +51 -5
- package/dist/core/init.ts +58 -5
- package/dist/core/invoked.d.ts +1 -1
- package/dist/core/invoked.js +3 -1
- package/dist/core/invoked.ts +5 -2
- package/dist/core/round.js +36 -22
- package/dist/core/round.ts +26 -9
- package/dist/core/rows.d.ts +4 -0
- package/dist/core/rows.js +31 -2
- package/dist/core/rows.ts +33 -1
- package/dist/core/scope.d.ts +1 -0
- package/dist/core/scope.js +1 -0
- package/dist/core/scope.ts +2 -0
- package/dist/core/thread.d.ts +3 -3
- package/dist/core/tree.d.ts +4 -1
- package/dist/core/tree.js +26 -6
- package/dist/core/tree.ts +29 -6
- package/dist/core/types.d.ts +3 -3
- package/dist/core/types.js +3 -1
- package/dist/core/types.ts +2 -1
- package/dist/derivation/attach.js +3 -2
- package/dist/derivation/attach.ts +2 -1
- package/dist/derivation/generators/build.js +6 -6
- package/dist/derivation/generators/build.ts +1 -1
- package/dist/derivation/generators/check.js +1 -1
- package/dist/derivation/generators/determinism.js +1 -1
- package/dist/derivation/generators/release.js +60 -47
- package/dist/derivation/generators/release.ts +66 -43
- package/dist/derivation/release.d.ts +7 -3
- package/dist/derivation/release.js +14 -1
- package/dist/derivation/release.ts +22 -2
- package/dist/integrations/attach.d.ts +4 -0
- package/dist/integrations/attach.js +37 -56
- package/dist/integrations/attach.ts +54 -60
- package/dist/integrations/cli.d.ts +37 -0
- package/dist/integrations/cli.js +440 -0
- package/dist/integrations/cli.ts +543 -0
- package/dist/{core/files.d.ts → integrations/disk.d.ts} +16 -24
- package/dist/integrations/disk.js +435 -0
- package/dist/integrations/disk.ts +465 -0
- package/dist/integrations/generators/read.js +6 -6
- package/dist/integrations/generators/read.ts +4 -4
- package/dist/integrations/generators/vault.js +10 -11
- package/dist/integrations/generators/vault.ts +4 -5
- package/dist/{operations/http.d.ts → integrations/https.d.ts} +10 -6
- package/dist/integrations/https.js +170 -0
- package/dist/integrations/https.ts +216 -0
- package/dist/integrations/pglite.d.ts +26 -0
- package/dist/integrations/pglite.js +143 -0
- package/dist/integrations/pglite.ts +206 -0
- package/dist/integrations/secret.d.ts +8 -2
- package/dist/integrations/secret.js +3 -5
- package/dist/integrations/secret.ts +10 -6
- package/dist/integrations/target.d.ts +155 -14
- package/dist/integrations/target.js +171 -50
- package/dist/integrations/target.ts +377 -61
- package/dist/integrations/vault.d.ts +1 -0
- package/dist/integrations/vault.js +20 -5
- package/dist/integrations/vault.ts +17 -5
- package/dist/integrity/attach.js +8 -77
- package/dist/integrity/attach.ts +7 -76
- package/dist/integrity/check-threads.js +5 -5
- package/dist/integrity/check-threads.ts +2 -2
- package/dist/integrity/constraint.d.ts +0 -1
- package/dist/integrity/constraint.js +267 -366
- package/dist/integrity/constraint.ts +267 -375
- package/dist/integrity/generators/compiler.js +14 -17
- package/dist/integrity/generators/compiler.ts +10 -15
- package/dist/integrity/generators/gate.js +15 -15
- package/dist/integrity/generators/gate.ts +10 -10
- package/dist/integrity/generators/issues.js +7 -7
- package/dist/integrity/generators/publish.js +39 -57
- package/dist/integrity/generators/publish.ts +44 -55
- package/dist/integrity/generators/trial.d.ts +3 -3
- package/dist/integrity/generators/trial.js +133 -165
- package/dist/integrity/generators/trial.ts +145 -169
- package/dist/integrity/lint.js +9 -13
- package/dist/integrity/lint.ts +7 -10
- package/dist/integrity/lsp/bin.js +5 -4
- package/dist/integrity/lsp/bin.ts +3 -1
- package/dist/integrity/lsp/diagnostics.js +3 -3
- package/dist/integrity/lsp/diagnostics.ts +1 -1
- package/dist/integrity/lsp/lint.js +19 -30
- package/dist/integrity/lsp/lint.ts +16 -32
- package/dist/integrity/lsp/server.js +3 -3
- package/dist/integrity/slopnator.d.ts +3 -13
- package/dist/integrity/slopnator.js +59 -38
- package/dist/integrity/slopnator.ts +54 -38
- package/dist/integrity/slopnators/sl_broken_lineage.js +2 -2
- package/dist/integrity/slopnators/sl_declared_elsewhere.js +1 -1
- package/dist/integrity/slopnators/sl_derived_drift.js +4 -4
- package/dist/integrity/slopnators/sl_derived_drift.ts +1 -1
- package/dist/integrity/slopnators/sl_door_in_a_node.js +220 -0
- package/dist/integrity/slopnators/sl_door_in_a_node.ts +250 -0
- package/dist/integrity/slopnators/sl_inert_island.js +10 -8
- package/dist/integrity/slopnators/sl_inert_island.ts +8 -6
- package/dist/integrity/slopnators/sl_missing_row.js +6 -51
- package/dist/integrity/slopnators/sl_missing_row.ts +4 -50
- package/dist/integrity/slopnators/sl_no_barrel.js +1 -1
- package/dist/integrity/slopnators/sl_no_child_process.js +36 -18
- package/dist/integrity/slopnators/sl_no_child_process.ts +44 -18
- package/dist/integrity/slopnators/sl_no_dispatch_bypass.js +80 -149
- package/dist/integrity/slopnators/sl_no_dispatch_bypass.ts +95 -147
- package/dist/integrity/slopnators/sl_no_import_cycle.js +15 -4
- package/dist/integrity/slopnators/sl_no_import_cycle.ts +20 -4
- package/dist/integrity/slopnators/sl_no_such_command.js +2 -2
- package/dist/integrity/slopnators/sl_no_such_command.ts +1 -1
- package/dist/integrity/slopnators/sl_outside_an_operation.js +20 -0
- package/dist/integrity/slopnators/sl_outside_an_operation.ts +25 -0
- package/dist/integrity/slopnators/sl_screen_holds_only_declared.d.ts +2 -0
- package/dist/integrity/slopnators/sl_screen_holds_only_declared.js +61 -0
- package/dist/integrity/slopnators/sl_screen_holds_only_declared.ts +70 -0
- package/dist/integrity/slopnators/sl_secret_literal.js +1 -33
- package/dist/integrity/slopnators/sl_secret_literal.ts +1 -33
- package/dist/integrity/slopnators/sl_stale_path.js +5 -22
- package/dist/integrity/slopnators/sl_stale_path.ts +4 -20
- package/dist/integrity/slopnators/sl_twice_meant.js +1 -1
- package/dist/integrity/slopnators/sl_twice_parented.js +29 -5
- package/dist/integrity/slopnators/sl_twice_parented.ts +28 -4
- package/dist/integrity/slopnators/sl_undeclared_crossing.d.ts +4 -1
- package/dist/integrity/slopnators/sl_undeclared_crossing.js +426 -62
- package/dist/integrity/slopnators/sl_undeclared_crossing.ts +454 -70
- package/dist/integrity/slopnators/sl_undeclared_destination.js +59 -25
- package/dist/integrity/slopnators/sl_undeclared_destination.ts +69 -21
- package/dist/integrity/slopnators/sl_undeclared_list.d.ts +2 -0
- package/dist/integrity/slopnators/sl_undeclared_list.js +363 -0
- package/dist/integrity/slopnators/sl_undeclared_list.ts +358 -0
- package/dist/integrity/slopnators/sl_undeclared_node.js +1 -11
- package/dist/integrity/slopnators/sl_undeclared_node.ts +0 -10
- package/dist/integrity/slopnators/sl_undeclared_operation.js +111 -29
- package/dist/integrity/slopnators/sl_undeclared_operation.ts +134 -26
- package/dist/integrity/slopnators/sl_undeclared_variant.js +2 -2
- package/dist/integrity/slopnators/sl_undeclared_webhook.d.ts +2 -0
- package/dist/integrity/slopnators/sl_undeclared_webhook.js +90 -0
- package/dist/integrity/slopnators/sl_undeclared_webhook.ts +108 -0
- package/dist/integrity/slopnators/sl_underived_route.d.ts +2 -0
- package/dist/integrity/slopnators/sl_underived_route.js +61 -0
- package/dist/integrity/slopnators/sl_underived_route.ts +72 -0
- package/dist/integrity/slopnators/sl_ungoverned_island.js +3 -3
- package/dist/integrity/slopnators/sl_ungoverned_island.ts +1 -1
- package/dist/integrity/slopnators/sl_unlisted_program.d.ts +2 -0
- package/dist/integrity/slopnators/sl_unlisted_program.js +39 -0
- package/dist/integrity/slopnators/sl_unlisted_program.ts +40 -0
- package/dist/integrity/slopnators/sl_unnamed_island.js +1 -1
- package/dist/integrity/slopnators/sl_unpaired_member.js +3 -3
- package/dist/integrity/slopnators/sl_unpaired_member.ts +1 -1
- package/dist/integrity/slopnators/sl_unpaired_slopnator.js +4 -4
- package/dist/integrity/slopnators/sl_unpaired_slopnator.ts +1 -1
- package/dist/integrity/slopnators/sl_unprefixed_member.js +5 -31
- package/dist/integrity/slopnators/sl_unprefixed_member.ts +2 -28
- package/dist/integrity/slopnators/sl_unthreaded.js +11 -7
- package/dist/integrity/slopnators/sl_unthreaded.ts +6 -7
- package/dist/integrity/slopnators/sl_untranslated_key.js +2 -25
- package/dist/integrity/slopnators/sl_untranslated_key.ts +1 -25
- package/dist/integrity/slopnators/sl_unvaulted_secret.d.ts +2 -0
- package/dist/integrity/slopnators/sl_unvaulted_secret.js +107 -0
- package/dist/integrity/slopnators/sl_unvaulted_secret.ts +132 -0
- package/dist/integrity/slopnators/sl_unversioned.js +6 -4
- package/dist/integrity/slopnators/sl_unversioned.ts +5 -3
- package/dist/integrity/sweep.d.ts +1 -0
- package/dist/integrity/sweep.js +105 -42
- package/dist/integrity/sweep.ts +115 -42
- package/dist/knowledge/attach.js +4 -3
- package/dist/knowledge/attach.ts +3 -2
- package/dist/knowledge/generators/docsgen.js +8 -6
- package/dist/knowledge/generators/docsgen.ts +4 -2
- package/dist/knowledge/generators/glossary.js +4 -4
- package/dist/knowledge/generators/glossary.ts +1 -1
- package/dist/operations/access.d.ts +23 -0
- package/dist/operations/access.js +28 -1
- package/dist/operations/access.ts +43 -0
- package/dist/operations/attach.js +33 -113
- package/dist/operations/attach.ts +51 -117
- package/dist/operations/capabilities.d.ts +14 -5
- package/dist/operations/capabilities.js +13 -4
- package/dist/operations/capabilities.ts +27 -8
- package/dist/operations/dispatch.d.ts +8 -2
- package/dist/operations/dispatch.js +128 -185
- package/dist/operations/dispatch.ts +138 -191
- package/dist/operations/error-results.d.ts +1 -1
- package/dist/operations/error-results.js +2 -2
- package/dist/operations/error-results.ts +2 -2
- package/dist/operations/generators/capability.d.ts +11 -1
- package/dist/operations/generators/capability.js +481 -224
- package/dist/operations/generators/capability.ts +562 -233
- package/dist/operations/generators/compile.js +66 -39
- package/dist/operations/generators/compile.ts +78 -39
- package/dist/operations/generators/permits.js +11 -12
- package/dist/operations/generators/permits.ts +3 -4
- package/dist/operations/generators/seed.d.ts +1 -0
- package/dist/operations/generators/seed.js +19 -17
- package/dist/operations/generators/seed.ts +22 -14
- package/dist/operations/generators/trial.d.ts +6 -8
- package/dist/operations/generators/trial.js +200 -70
- package/dist/operations/generators/trial.ts +230 -93
- package/dist/operations/nodes.d.ts +15 -16
- package/dist/operations/nodes.ts +16 -17
- package/dist/operations/operations.d.ts +40 -4
- package/dist/operations/operations.js +1 -0
- package/dist/operations/operations.ts +61 -4
- package/dist/operations/result.d.ts +2 -2
- package/dist/operations/result.ts +2 -2
- package/package.json +1 -4
- package/dist/core/answers.d.ts +0 -171
- package/dist/core/answers.js +0 -789
- package/dist/core/answers.ts +0 -999
- package/dist/core/client.d.ts +0 -63
- package/dist/core/client.js +0 -249
- package/dist/core/client.ts +0 -289
- package/dist/core/files.js +0 -171
- package/dist/core/files.ts +0 -222
- package/dist/integrations/module.d.ts +0 -6
- package/dist/integrations/module.js +0 -153
- package/dist/integrations/module.ts +0 -191
- package/dist/integrity/slopnators/sl_unkept_rows.js +0 -104
- package/dist/integrity/slopnators/sl_unkept_rows.ts +0 -111
- package/dist/integrity/slopnators/sl_unseamed_reach.js +0 -73
- package/dist/integrity/slopnators/sl_unseamed_reach.ts +0 -79
- package/dist/operations/cost.d.ts +0 -14
- package/dist/operations/cost.js +0 -37
- package/dist/operations/cost.ts +0 -58
- package/dist/operations/errors/errors.d.ts +0 -52
- package/dist/operations/http.js +0 -124
- package/dist/operations/http.ts +0 -169
- package/dist/operations/memory.d.ts +0 -104
- package/dist/operations/memory.js +0 -365
- package/dist/operations/memory.ts +0 -470
- package/dist/operations/metering.d.ts +0 -55
- package/dist/operations/metering.js +0 -247
- package/dist/operations/metering.ts +0 -328
- package/dist/operations/queue-policy.d.ts +0 -3
- package/dist/operations/queue-policy.js +0 -8
- package/dist/operations/queue-policy.ts +0 -10
- package/dist/operations/seams.d.ts +0 -239
- package/dist/operations/seams.js +0 -45
- package/dist/operations/seams.ts +0 -364
- package/dist/operations/store.d.ts +0 -14
- package/dist/operations/store.js +0 -1
- package/dist/operations/store.ts +0 -29
- /package/dist/{operations → core}/errors/classes.d.ts +0 -0
- /package/dist/{operations → core}/errors/classes.js +0 -0
- /package/dist/{operations → core}/errors/classes.ts +0 -0
- /package/dist/integrity/slopnators/{sl_unkept_rows.d.ts → sl_door_in_a_node.d.ts} +0 -0
- /package/dist/integrity/slopnators/{sl_unseamed_reach.d.ts → sl_outside_an_operation.d.ts} +0 -0
|
@@ -1,12 +1,21 @@
|
|
|
1
|
-
export const SURFACES = ['cli', 'api', 'mcp'];
|
|
1
|
+
export const SURFACES = ['cli', 'api', 'mcp', 'webhook'];
|
|
2
2
|
export const isSurface = (one) => SURFACES.includes(one);
|
|
3
|
+
export const MEANS = {
|
|
4
|
+
cli: 'a command a terminal runs',
|
|
5
|
+
api: 'a route a request arrives on, and the OpenAPI document written from it. An operation that does not answer on api has no route file and no route row at all',
|
|
6
|
+
mcp: 'a tool an agent is offered. Switch this off and an AI actor cannot call the operation, while everything else about it goes on working',
|
|
7
|
+
webhook: 'an address something outside posts to, once a webhook row invokes the operation and the operation names it back',
|
|
8
|
+
};
|
|
9
|
+
export const DEFAULTS_AT = '_threads/defaults.ts';
|
|
10
|
+
export const UNSTATED = ['cli'];
|
|
3
11
|
export const CAPABILITIES_AT = 'operations/_capabilities.json';
|
|
4
12
|
export const CLIENT_AT = 'operations/_client.ts';
|
|
5
13
|
export const DISPATCHING_AT = 'app/_generated/dispatching.ts';
|
|
14
|
+
export const REACHING_AT = 'src/_generated/reaching.ts';
|
|
15
|
+
export const ACTOR_AT = 'src/_generated/actor.ts';
|
|
6
16
|
export const routeAt = (operation) => `app/operations/${operation}/route.ts`;
|
|
7
|
-
export const ROUTE =
|
|
8
|
-
export const
|
|
9
|
-
export const DECLARED_SEAMS_AT = 'src/_generated/seams.ts';
|
|
17
|
+
export const ROUTE = "The file that answers a request for this one operation. It imports the operation by name, hands it to this app's own dispatch along with what this app hands its operations and the caller worked out from the request, and answers with the result — or with the status matching whatever dispatch refused it for. One per operation rather than one for all of them: a route that looked its operation up by a name off the path would hand dispatch something no check had ever seen, which reads exactly like a name that was checked. An operation that does not answer on api, because its own row turns api off or the defaults sheet leaves it off, has neither this file nor this row: `thread capabilities` takes both out, so it is not served at all rather than served and turned away.";
|
|
18
|
+
export const inboundAt = (webhook) => `app/inbound/${webhook}/route.ts`;
|
|
10
19
|
export const OPENAPI_AT = 'operations/_openapi.json';
|
|
11
20
|
export const MCP_AT = 'operations/_mcp.json';
|
|
12
21
|
export const reachable = (one, surface) => one.live.includes(surface);
|
|
@@ -1,25 +1,42 @@
|
|
|
1
|
+
import type { Threaded } from '../core/thread.ts'
|
|
1
2
|
|
|
2
|
-
|
|
3
|
-
export const SURFACES = ['cli', 'api', 'mcp'] as const
|
|
3
|
+
export const SURFACES = ['cli', 'api', 'mcp', 'webhook'] as const
|
|
4
4
|
|
|
5
5
|
export type Surface = (typeof SURFACES)[number]
|
|
6
6
|
|
|
7
7
|
export const isSurface = (one: string): one is Surface => (SURFACES as readonly string[]).includes(one)
|
|
8
8
|
|
|
9
|
+
export const MEANS: Readonly<Record<Surface, string>> = {
|
|
10
|
+
cli: 'a command a terminal runs',
|
|
11
|
+
api: 'a route a request arrives on, and the OpenAPI document written from it. An operation that does not answer on api has no route file and no route row at all',
|
|
12
|
+
mcp: 'a tool an agent is offered. Switch this off and an AI actor cannot call the operation, while everything else about it goes on working',
|
|
13
|
+
webhook: 'an address something outside posts to, once a webhook row invokes the operation and the operation names it back',
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const DEFAULTS_AT = '_threads/defaults.ts'
|
|
17
|
+
|
|
18
|
+
export const UNSTATED: readonly Surface[] = ['cli']
|
|
19
|
+
|
|
20
|
+
export interface Defaults extends Threaded {
|
|
21
|
+
readonly live?: readonly Surface[]
|
|
22
|
+
}
|
|
23
|
+
|
|
9
24
|
export const CAPABILITIES_AT = 'operations/_capabilities.json'
|
|
10
25
|
|
|
11
26
|
export const CLIENT_AT = 'operations/_client.ts'
|
|
12
27
|
|
|
13
28
|
export const DISPATCHING_AT = 'app/_generated/dispatching.ts'
|
|
14
29
|
|
|
30
|
+
export const REACHING_AT = 'src/_generated/reaching.ts'
|
|
31
|
+
|
|
32
|
+
export const ACTOR_AT = 'src/_generated/actor.ts'
|
|
33
|
+
|
|
15
34
|
export const routeAt = (operation: string): string => `app/operations/${operation}/route.ts`
|
|
16
35
|
|
|
17
36
|
export const ROUTE =
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
export const SEAMS_AT = 'src/seams.ts'
|
|
37
|
+
"The file that answers a request for this one operation. It imports the operation by name, hands it to this app's own dispatch along with what this app hands its operations and the caller worked out from the request, and answers with the result — or with the status matching whatever dispatch refused it for. One per operation rather than one for all of them: a route that looked its operation up by a name off the path would hand dispatch something no check had ever seen, which reads exactly like a name that was checked. An operation that does not answer on api, because its own row turns api off or the defaults sheet leaves it off, has neither this file nor this row: `thread capabilities` takes both out, so it is not served at all rather than served and turned away."
|
|
21
38
|
|
|
22
|
-
export const
|
|
39
|
+
export const inboundAt = (webhook: string): string => `app/inbound/${webhook}/route.ts`
|
|
23
40
|
|
|
24
41
|
export const OPENAPI_AT = 'operations/_openapi.json'
|
|
25
42
|
|
|
@@ -34,9 +51,11 @@ export interface Capability {
|
|
|
34
51
|
|
|
35
52
|
readonly live: readonly Surface[]
|
|
36
53
|
|
|
37
|
-
readonly
|
|
54
|
+
readonly webhooks: readonly string[]
|
|
55
|
+
|
|
56
|
+
readonly signed: boolean
|
|
38
57
|
|
|
39
|
-
readonly
|
|
58
|
+
readonly browsable: boolean
|
|
40
59
|
|
|
41
60
|
readonly mcp: { readonly name: string; readonly description: string; readonly inputSchema: unknown }
|
|
42
61
|
readonly cli: { readonly invocation: string; readonly describes: string }
|
|
@@ -1,14 +1,20 @@
|
|
|
1
1
|
export declare const DISPATCH_AT = "operations/_dispatch.ts";
|
|
2
2
|
export declare const dispatched: (reached: Readonly<Record<string, {
|
|
3
3
|
readonly reach: string;
|
|
4
|
-
readonly address
|
|
4
|
+
readonly address?: string;
|
|
5
|
+
readonly headers: Readonly<Record<string, string>>;
|
|
5
6
|
readonly sends: readonly {
|
|
6
7
|
readonly secret: string;
|
|
7
8
|
readonly header: string;
|
|
8
9
|
readonly prefix: string;
|
|
9
10
|
}[];
|
|
11
|
+
readonly signs: readonly {
|
|
12
|
+
readonly secret: string;
|
|
13
|
+
readonly header: string;
|
|
14
|
+
readonly prefix: string;
|
|
15
|
+
}[];
|
|
10
16
|
readonly calls: Readonly<Record<string, {
|
|
11
|
-
readonly method: string;
|
|
17
|
+
readonly method: string | null;
|
|
12
18
|
readonly path: string;
|
|
13
19
|
}>>;
|
|
14
20
|
}>>) => string;
|
|
@@ -1,49 +1,52 @@
|
|
|
1
|
-
import { ANONYMOUS, ENVIRONMENT } from
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
1
|
+
import { ANONYMOUS, ENVIRONMENT } from '../core/actor.js';
|
|
2
|
+
import { HOLDS } from '../integrations/target.js';
|
|
3
|
+
import { PERMITS_AT } from './access.js';
|
|
4
|
+
import { DbConflict } from '../core/errors/catalogue.js';
|
|
5
|
+
import { beside, quoted, runtime } from './generators/seed.js';
|
|
6
|
+
const reaching = (holds) => Object.keys(HOLDS).filter((one) => holds(HOLDS[one]));
|
|
7
|
+
const KEEPS = reaching((one) => one.takes === 'entities');
|
|
8
|
+
const ANSWERS = reaching((one) => one.takes === null);
|
|
9
|
+
const RUNS = reaching((one) => one.takes === 'calls' && one.addressed === 'nothing');
|
|
10
|
+
const asked = (reaches) => reaches.map((one) => "reach === '" + one + "'").join(' || ');
|
|
6
11
|
export const DISPATCH_AT = 'operations/_dispatch.ts';
|
|
7
|
-
const identity = (code) => {
|
|
8
|
-
const def = errorDefForResult(code);
|
|
9
|
-
return { code: def.code, class: def.class };
|
|
10
|
-
};
|
|
11
|
-
const CODES = ['denied', 'invalid', 'rate_limited', 'feature_denied', 'quota_exceeded', 'error'];
|
|
12
12
|
const REFUSAL = {
|
|
13
|
-
denied:
|
|
14
|
-
invalid:
|
|
15
|
-
rate_limited:
|
|
16
|
-
feature_denied:
|
|
17
|
-
quota_exceeded:
|
|
18
|
-
error:
|
|
13
|
+
denied: 'not allowed',
|
|
14
|
+
invalid: 'invalid input',
|
|
15
|
+
rate_limited: 'rate limited',
|
|
16
|
+
feature_denied: 'not in plan',
|
|
17
|
+
quota_exceeded: 'quota exceeded',
|
|
18
|
+
error: 'failed',
|
|
19
19
|
};
|
|
20
20
|
const printed = (value) => JSON.stringify(value, null, 2);
|
|
21
|
-
const DEFS = Object.fromEntries(CODES.map((code) => [code, identity(code)]));
|
|
22
21
|
export const dispatched = (reached) => [
|
|
22
|
+
"import { createHmac } from 'node:crypto'",
|
|
23
|
+
'',
|
|
23
24
|
`import type { Actor } from ${quoted(runtime(DISPATCH_AT, '../../core/actor.ts'))}`,
|
|
24
|
-
`import type { Operation } from ${quoted(runtime(DISPATCH_AT, '../operations.ts'))}`,
|
|
25
|
+
`import type { Allowance, Counting, Operation } from ${quoted(runtime(DISPATCH_AT, '../operations.ts'))}`,
|
|
25
26
|
`import type { FailureIdentity, Result, ResultCode } from ${quoted(runtime(DISPATCH_AT, '../result.ts'))}`,
|
|
26
|
-
|
|
27
|
+
'import type { Opened } from ' + quoted(runtime(DISPATCH_AT, '../operations.ts')),
|
|
28
|
+
'import type { Vault } from ' + quoted(runtime(DISPATCH_AT, '../../integrations/secret.ts')),
|
|
29
|
+
`import { permits } from ${quoted(beside(DISPATCH_AT, PERMITS_AT))}`,
|
|
27
30
|
'',
|
|
28
31
|
`const ANONYMOUS: Actor = ${printed(ANONYMOUS)}`,
|
|
29
32
|
'',
|
|
30
33
|
`const ENVIRONMENT: readonly string[] = ${printed(ENVIRONMENT.map(([fact]) => fact))}`,
|
|
31
34
|
'',
|
|
32
|
-
`const REFUSAL: Readonly<Record<ResultCode,
|
|
33
|
-
'',
|
|
34
|
-
`const DEFS = ${printed(DEFS)} as Readonly<Record<ResultCode, FailureIdentity>>`,
|
|
35
|
+
`const REFUSAL: Readonly<Record<ResultCode, string>> = ${printed(REFUSAL)}`,
|
|
35
36
|
'',
|
|
36
37
|
`const CONFLICT = ${printed({ code: DbConflict.def.code, class: DbConflict.def.class })} as FailureIdentity`,
|
|
37
38
|
'',
|
|
38
|
-
`const SUPPLY: Readonly<Record<string, string>> = ${printed(SUPPLY)}`,
|
|
39
|
-
'',
|
|
40
39
|
'type Reached = Readonly<Record<string, {',
|
|
41
40
|
' readonly reach: string',
|
|
42
|
-
' readonly address
|
|
41
|
+
' readonly address?: string',
|
|
42
|
+
' readonly headers: Readonly<Record<string, string>>',
|
|
43
43
|
' readonly sends: readonly { readonly secret: string; readonly header: string; readonly prefix: string }[]',
|
|
44
|
-
' readonly
|
|
44
|
+
' readonly signs: readonly { readonly secret: string; readonly header: string; readonly prefix: string }[]',
|
|
45
|
+
' readonly calls: Readonly<Record<string, { readonly method: string | null; readonly path: string }>>',
|
|
45
46
|
'}>>',
|
|
46
47
|
'',
|
|
48
|
+
'type Reaches = Readonly<Record<string, unknown>>',
|
|
49
|
+
'',
|
|
47
50
|
`const REACHED: Reached = ${printed(reached)}`,
|
|
48
51
|
'',
|
|
49
52
|
"const STOPPED = Symbol.for('thread.operation.stopped')",
|
|
@@ -58,6 +61,22 @@ export const dispatched = (reached) => [
|
|
|
58
61
|
' ...(identity === undefined ? {} : { errorCode: identity.code, errorClass: identity.class }),',
|
|
59
62
|
'}) as Result<never>',
|
|
60
63
|
'',
|
|
64
|
+
'const allowance = async (',
|
|
65
|
+
' id: string,',
|
|
66
|
+
' actor: Actor,',
|
|
67
|
+
' counting: Counting | undefined,',
|
|
68
|
+
' held: Allowance | undefined,',
|
|
69
|
+
' code: ResultCode,',
|
|
70
|
+
'): Promise<Result<never> | undefined> => {',
|
|
71
|
+
' if (held === undefined) return undefined',
|
|
72
|
+
' if (counting === undefined) {',
|
|
73
|
+
" return fail('error', REFUSAL['error'], id + ' allows ' + String(held.runs) + ' run(s) per ' + held.per + ' and this dispatch was handed nothing to count with. An operation that states an allowance is handed a counter or it does not run')",
|
|
74
|
+
' }',
|
|
75
|
+
" const seen = await counting['seen'](id + '/' + String(actor.id ?? 'anonymous') + '/' + code, held.per)",
|
|
76
|
+
' if (seen <= held.runs) return undefined',
|
|
77
|
+
" return fail(code, REFUSAL[code], String(seen) + ' of ' + String(held.runs) + ' allowed per ' + held.per)",
|
|
78
|
+
'}',
|
|
79
|
+
'',
|
|
61
80
|
"const strings = (value: unknown): value is string[] => Array.isArray(value) && value.every((one) => typeof one === 'string')",
|
|
62
81
|
'',
|
|
63
82
|
'const person = (value: unknown): Actor | undefined => {',
|
|
@@ -90,12 +109,6 @@ export const dispatched = (reached) => [
|
|
|
90
109
|
" return one[STOPPED] === true ? { value: one['value'] } : undefined",
|
|
91
110
|
'}',
|
|
92
111
|
'',
|
|
93
|
-
'const marked = (thrown: unknown): string | undefined => {',
|
|
94
|
-
" if (typeof thrown !== 'object' || thrown === null) return undefined",
|
|
95
|
-
' const said = (thrown as { readonly node?: unknown }).node',
|
|
96
|
-
" return typeof said === 'string' ? said : undefined",
|
|
97
|
-
'}',
|
|
98
|
-
'',
|
|
99
112
|
'const platform = (thrown: unknown): string | undefined => {',
|
|
100
113
|
" if (typeof thrown !== 'object' || thrown === null) return undefined",
|
|
101
114
|
' const said = (thrown as { readonly because?: unknown }).because',
|
|
@@ -110,59 +123,88 @@ export const dispatched = (reached) => [
|
|
|
110
123
|
' return held',
|
|
111
124
|
'}',
|
|
112
125
|
'',
|
|
113
|
-
'const bound = (word: string, wired: Reached[string],
|
|
126
|
+
'const bound = (word: string, wired: Reached[string], opened: Reaches, opening: Vault | undefined, within: number): Record<string, unknown> | string => {',
|
|
127
|
+
' const reached = opened[word]',
|
|
128
|
+
' const { reach } = wired',
|
|
129
|
+
' if (' + asked(ANSWERS) + ') {',
|
|
130
|
+
' const held = reached',
|
|
131
|
+
' if (held === undefined) {',
|
|
132
|
+
" return word + ' is a disk this operation requires, and this dispatch was handed none under that name. The disk under this process is written in by thread capabilities; one at the other end of a connection is handed in by whoever opened it'",
|
|
133
|
+
' }',
|
|
134
|
+
' return held as unknown as Record<string, unknown>',
|
|
135
|
+
' }',
|
|
136
|
+
' if (' + asked(KEEPS) + ') {',
|
|
137
|
+
' if (reached === undefined) {',
|
|
138
|
+
" return word + ' keeps rows for this app, and this dispatch was handed no store. A store is written in by thread capabilities, out of the target that keeps the entity'",
|
|
139
|
+
' }',
|
|
140
|
+
' return reached as unknown as Record<string, unknown>',
|
|
141
|
+
' }',
|
|
142
|
+
'',
|
|
114
143
|
' const names = Object.keys(wired.calls)',
|
|
115
144
|
' if (names.length === 0) {',
|
|
116
145
|
' return `\\`${word}\\` is required and takes no call, so there is nothing to hand a node. Declare one with \\`thread create target call\\``',
|
|
117
146
|
' }',
|
|
118
|
-
' if (
|
|
119
|
-
'
|
|
147
|
+
' if (' + asked(RUNS) + ') {',
|
|
148
|
+
' const held = reached',
|
|
149
|
+
' if (held === undefined) {',
|
|
150
|
+
" return word + ' is a program this operation requires, and this dispatch was handed none under that name. A program is written in by thread capabilities, out of the calls declared beside the target'",
|
|
151
|
+
' }',
|
|
152
|
+
' const out: Record<string, unknown> = {}',
|
|
153
|
+
' for (const name of names) {',
|
|
154
|
+
' const doing = (held as unknown as Record<string, unknown>)[name]',
|
|
155
|
+
" if (typeof doing !== 'function') {",
|
|
156
|
+
" return word + ' takes ' + name + ' and the program handed in under that name does not answer to it, so a node would be handed a word that does nothing'",
|
|
157
|
+
' }',
|
|
158
|
+
' out[name] = (args: readonly string[], shows?: boolean, sooner?: number, how?: unknown) => (doing as (a: readonly string[], s?: boolean, w?: number, h?: unknown) => unknown)(args, shows, sooner === undefined ? within : Math.min(sooner, within), how)',
|
|
159
|
+
' }',
|
|
160
|
+
' return out',
|
|
161
|
+
' }',
|
|
162
|
+
' if (reached === undefined) {',
|
|
163
|
+
" return word + ' is reached over ' + wired.reach + ', and this dispatch was handed nothing to send an outward call through. thread capabilities writes that in; or take that word off the row that requires it'",
|
|
120
164
|
' }',
|
|
121
|
-
' const wanted = [...
|
|
122
|
-
' if (wanted.length > 0 &&
|
|
123
|
-
|
|
165
|
+
' const wanted = [...wired.sends.map((one) => one.secret), ...wired.signs.map((one) => one.secret)]',
|
|
166
|
+
' if (wanted.length > 0 && opening === undefined) {',
|
|
167
|
+
" return word + ' needs ' + wanted.join(', ') + ' and this dispatch was handed no vault to open them. A target is reached with what opens it or not at all'",
|
|
124
168
|
' }',
|
|
125
169
|
' const out: Record<string, unknown> = {}',
|
|
126
170
|
' for (const name of names) {',
|
|
127
171
|
' const call = wired.calls[name]',
|
|
128
172
|
' if (call === undefined) continue',
|
|
129
|
-
' out[name] = async (given?: { fills?: Readonly<Record<string, string | number>>; json?: unknown; body?: Uint8Array; contentType?: string }) => {',
|
|
130
|
-
|
|
173
|
+
' out[name] = async (given?: { fills?: Readonly<Record<string, string | number>>; json?: unknown; body?: Uint8Array; contentType?: string; told?: (said: unknown) => void }) => {',
|
|
174
|
+
" const values = wanted.length === 0 ? {} : await opening",
|
|
131
175
|
' const headers: Record<string, string> = {}',
|
|
176
|
+
' for (const [name, value] of Object.entries(wired.headers)) headers[name] = value',
|
|
132
177
|
' for (const one of wired.sends) headers[one.header] = `${one.prefix}${values[one.secret] ?? \'\'}`',
|
|
133
178
|
' const body = given?.json === undefined ? given?.body : new TextEncoder().encode(JSON.stringify(given.json))',
|
|
134
|
-
'
|
|
179
|
+
' for (const one of wired.signs) {',
|
|
180
|
+
" const mac = createHmac('sha256', values[one.secret] ?? '').update(body ?? new Uint8Array()).digest('hex')",
|
|
181
|
+
' headers[one.header] = one.prefix + mac',
|
|
182
|
+
' }',
|
|
183
|
+
" const send = reached as { call: (request: unknown, within?: number) => Promise<unknown> }",
|
|
184
|
+
" return await send['call']({",
|
|
135
185
|
' target: word,',
|
|
136
|
-
' address: wired.address
|
|
186
|
+
' address: wired.address ?? \'\',',
|
|
137
187
|
" method: call.method as 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE',",
|
|
138
188
|
' path: filled(call.path, given?.fills),',
|
|
139
189
|
' headers,',
|
|
140
190
|
' ...(body === undefined ? {} : { body, contentType: given?.contentType ?? \'application/json\' }),',
|
|
141
|
-
'
|
|
191
|
+
' ...(given?.told === undefined ? {} : { told: given.told }),',
|
|
192
|
+
' }, within)',
|
|
142
193
|
' }',
|
|
143
194
|
' }',
|
|
144
195
|
' return out',
|
|
145
196
|
'}',
|
|
146
197
|
'',
|
|
147
|
-
'const given = (operation: Operation<unknown, string>, actor: Actor,
|
|
148
|
-
' const held: Record<string, unknown> = { actor }',
|
|
198
|
+
'const given = (operation: Operation<unknown, string>, actor: Actor, opened: Reaches, opening: Vault | undefined, telling: (said: unknown) => void): Record<string, unknown> | string => {',
|
|
199
|
+
' const held: Record<string, unknown> = { actor, tell: telling }',
|
|
149
200
|
' for (const word of operation.requires) {',
|
|
150
|
-
' const
|
|
151
|
-
' if (
|
|
152
|
-
'
|
|
153
|
-
' if (wired === undefined) {',
|
|
154
|
-
' return `\\`${operation.id}\\` requires \\`${word}\\`, and nothing here supplies it — what may be required is ${[...Object.keys(SUPPLY), ...Object.keys(REACHED)].join(\', \')}`',
|
|
155
|
-
' }',
|
|
156
|
-
' const reached = bound(word, wired, seams)',
|
|
157
|
-
" if (typeof reached === 'string') return reached",
|
|
158
|
-
' held[word] = reached',
|
|
159
|
-
' continue',
|
|
160
|
-
' }',
|
|
161
|
-
' const seam = (seams as unknown as Record<string, unknown>)[key]',
|
|
162
|
-
' if (seam === undefined) {',
|
|
163
|
-
' return `\\`${operation.id}\\` requires \\`${word}\\`, and the seams this dispatch was handed hold no \\`${key}\\` — wire one, or take \\`${word}\\` off that operation\'s row. An operation does not run short of what it declared`',
|
|
201
|
+
' const wired = REACHED[word]',
|
|
202
|
+
' if (wired === undefined) {',
|
|
203
|
+
' return `\\`${operation[\'id\']}\\` requires \\`${word}\\`, and this app declares no target by that name. An operation requires targets: declare one with \\`thread create target\\`, or take that word off its row`',
|
|
164
204
|
' }',
|
|
165
|
-
'
|
|
205
|
+
' const reached = bound(word, wired, opened, opening, operation.timeout)',
|
|
206
|
+
" if (typeof reached === 'string') return reached",
|
|
207
|
+
' held[word] = reached',
|
|
166
208
|
' }',
|
|
167
209
|
' return held',
|
|
168
210
|
'}',
|
|
@@ -171,155 +213,56 @@ export const dispatched = (reached) => [
|
|
|
171
213
|
' operation: Operation<I, R>,',
|
|
172
214
|
' rawInput: unknown,',
|
|
173
215
|
' asking: Actor,',
|
|
174
|
-
'
|
|
216
|
+
' opened: Reaches,',
|
|
217
|
+
' opening: Vault | undefined,',
|
|
218
|
+
' counting?: Counting,',
|
|
219
|
+
' telling?: (said: unknown) => void,',
|
|
175
220
|
'): Promise<Result<unknown>> {',
|
|
176
221
|
' const id = operation.id',
|
|
177
222
|
' const who = person(asking)',
|
|
178
|
-
' const
|
|
179
|
-
' const actor: Actor = { ...(who ?? ANONYMOUS), run }',
|
|
180
|
-
' const span = seams.telemetry?.start(id, run, { ...actor })',
|
|
181
|
-
'',
|
|
182
|
-
' const gates = { rate: seams.rateLimiter !== undefined, replay: seams.idempotency !== undefined, quota: seams.quotas !== undefined, telemetry: seams.telemetry !== undefined }',
|
|
183
|
-
'',
|
|
184
|
-
' let hold: QuotaHold | null = null',
|
|
185
|
-
' let granted: string | undefined',
|
|
186
|
-
'',
|
|
187
|
-
' const sank = (what: string) => (thrown: unknown): void => {',
|
|
188
|
-
" seams.log.write('error', id, actor, {",
|
|
189
|
-
' [`${what}_failed`]: thrown instanceof Error ? thrown.message : String(thrown),',
|
|
190
|
-
' })',
|
|
191
|
-
' }',
|
|
192
|
-
'',
|
|
193
|
-
' const settled = async (held: Result<unknown>, because?: string): Promise<Result<unknown>> => {',
|
|
194
|
-
" const outcome: AuditOutcome = held.ok ? 'ok' : REFUSAL[held.code].outcome",
|
|
195
|
-
' const detail = because === undefined ? {} : { because }',
|
|
196
|
-
'',
|
|
197
|
-
' const reserved = hold === null ? null : operation.estimate',
|
|
198
|
-
' let consumed: number | null = null',
|
|
199
|
-
'',
|
|
200
|
-
' if (hold !== null) {',
|
|
201
|
-
' const settling = held.ok ? hold.commit(operation.estimate) : hold.release()',
|
|
202
|
-
' consumed = held.ok ? operation.estimate : 0',
|
|
203
|
-
' hold = null',
|
|
204
|
-
" await settling.catch(sank('quota'))",
|
|
205
|
-
' }',
|
|
206
|
-
'',
|
|
207
|
-
" seams.log.write(held.ok ? 'info' : 'warn', id, actor, {",
|
|
208
|
-
' outcome,',
|
|
209
|
-
' ...detail,',
|
|
210
|
-
" access: { roles: actor.roles, entitlements: actor.entitlements, granted: granted ?? 'not reached' },",
|
|
211
|
-
' quota: { meter: id, reserved, consumed },',
|
|
212
|
-
' gates,',
|
|
213
|
-
' })',
|
|
214
|
-
' if (held.ok) span?.ok()',
|
|
215
|
-
' else span?.error(outcome)',
|
|
216
|
-
'',
|
|
217
|
-
' await seams.audit',
|
|
218
|
-
" .append(actor, { action: id, outcome, subject: { type: 'operation', id }, ...detail })",
|
|
219
|
-
" .catch(sank('audit'))",
|
|
220
|
-
'',
|
|
221
|
-
' if (!held.ok) {',
|
|
222
|
-
' const identity: FailureIdentity =',
|
|
223
|
-
' held.errorCode === undefined || held.errorClass === undefined',
|
|
224
|
-
' ? DEFS[held.code]',
|
|
225
|
-
' : { code: held.errorCode, class: held.errorClass }',
|
|
226
|
-
'',
|
|
227
|
-
' await seams.errors',
|
|
228
|
-
' .record(actor, { code: identity.code, class: identity.class, action: id, ...detail })',
|
|
229
|
-
" .catch(sank('errors'))",
|
|
230
|
-
' }',
|
|
231
|
-
'',
|
|
232
|
-
' return held',
|
|
233
|
-
' }',
|
|
234
|
-
'',
|
|
235
|
-
' const refused = (code: ResultCode, because?: string): Promise<Result<unknown>> =>',
|
|
236
|
-
' settled(fail(code, REFUSAL[code].said, because), because)',
|
|
237
|
-
'',
|
|
238
|
-
' const recorded = (began: number, ok: boolean, at: string | undefined, because?: string): void => {',
|
|
239
|
-
' const took = seams.clock.now() - began',
|
|
240
|
-
' const sections = {',
|
|
241
|
-
' node: { at: at ?? null },',
|
|
242
|
-
' audit: { actor, when: began },',
|
|
243
|
-
' observability: { ms: took, ok, ...(because === undefined ? {} : { because }) },',
|
|
244
|
-
' }',
|
|
245
|
-
'',
|
|
246
|
-
" seams.log.write(ok ? 'info' : 'warn', id, actor, sections)",
|
|
247
|
-
' seams.telemetry?.record?.({',
|
|
248
|
-
' key: id,',
|
|
249
|
-
' traceId: run,',
|
|
250
|
-
' spanId: run,',
|
|
251
|
-
' durationMs: took,',
|
|
252
|
-
' ok,',
|
|
253
|
-
' attrs: sections,',
|
|
254
|
-
' actor,',
|
|
255
|
-
' ...(at === undefined ? {} : { nodeId: at }),',
|
|
256
|
-
' workflowId: id,',
|
|
257
|
-
' ...(because === undefined ? {} : { error: because }),',
|
|
258
|
-
' })',
|
|
259
|
-
' }',
|
|
260
|
-
'',
|
|
223
|
+
' const actor: Actor = who ?? ANONYMOUS',
|
|
261
224
|
' try {',
|
|
262
|
-
" if (who === undefined) return
|
|
263
|
-
'',
|
|
264
|
-
" if (seams.rateLimiter !== undefined && !(await seams.rateLimiter.check(actor, id))) return await refused('rate_limited')",
|
|
265
|
-
'',
|
|
266
|
-
' const replayed = seams.idempotency === undefined ? null : await seams.idempotency.check(actor.replay, id)',
|
|
267
|
-
" if (replayed !== null && replayed !== undefined) return await settled(ok(replayed), 'replayed')",
|
|
268
|
-
'',
|
|
225
|
+
" if (who === undefined) return fail('denied', REFUSAL['denied'], 'identity_invalid')",
|
|
269
226
|
' const parsed = operation.input.safeParse(rawInput)',
|
|
270
227
|
' if (!parsed.success) {',
|
|
271
|
-
" return
|
|
228
|
+
" return fail('invalid', REFUSAL['invalid'], String(parsed['error']))",
|
|
272
229
|
' }',
|
|
273
|
-
'',
|
|
274
|
-
'
|
|
275
|
-
'
|
|
276
|
-
"
|
|
277
|
-
'
|
|
278
|
-
' }',
|
|
279
|
-
'',
|
|
280
|
-
|
|
281
|
-
" return await refused('denied', 'no permits check is wired, so nothing can permit this operation')",
|
|
230
|
+
' const { resource } = operation',
|
|
231
|
+
' const ruled = permits(actor, id, resource(parsed[\'data\']))',
|
|
232
|
+
' const { allow, because: why } = ruled',
|
|
233
|
+
" if (!allow) return fail('denied', REFUSAL['denied'], why)",
|
|
234
|
+
'',
|
|
235
|
+
' const { entitled } = operation',
|
|
236
|
+
' if (entitled !== undefined && !actor.entitlements.includes(entitled)) {',
|
|
237
|
+
" return fail('feature_denied', REFUSAL['feature_denied'], id + ' is offered on ' + entitled + ', which this caller does not hold')",
|
|
282
238
|
' }',
|
|
283
239
|
'',
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
240
|
+
" const limited = await allowance(id, actor, counting, operation.rate, 'rate_limited')",
|
|
241
|
+
' if (limited !== undefined) return limited',
|
|
242
|
+
" const over = await allowance(id, actor, counting, operation.quota, 'quota_exceeded')",
|
|
243
|
+
' if (over !== undefined) return over',
|
|
287
244
|
'',
|
|
288
|
-
' const
|
|
289
|
-
" if (typeof
|
|
245
|
+
' const handed = given(operation as Operation<unknown, string>, actor, opened, opening, telling ?? (() => undefined))',
|
|
246
|
+
" if (typeof handed === 'string') return fail('error', REFUSAL['error'], handed)",
|
|
290
247
|
'',
|
|
291
248
|
' const held: { readonly input: I } & Record<string, unknown> = { input: parsed.data }',
|
|
292
|
-
' const
|
|
293
|
-
'
|
|
294
|
-
' try {',
|
|
295
|
-
' value = await operation.run(hands as Handed<R>, held)',
|
|
296
|
-
' } catch (thrown) {',
|
|
297
|
-
' recorded(began, stopped(thrown) !== undefined, marked(thrown), thrown instanceof Error ? thrown.message : String(thrown))',
|
|
298
|
-
' throw thrown',
|
|
299
|
-
' }',
|
|
300
|
-
' recorded(began, true, undefined)',
|
|
301
|
-
'',
|
|
302
|
-
" if (seams.idempotency !== undefined) await seams.idempotency.store(actor.replay, id, value).catch(sank('idempotency'))",
|
|
303
|
-
'',
|
|
304
|
-
' return await settled(ok(value))',
|
|
249
|
+
' const value = await operation.run(handed as Opened<R>, held)',
|
|
250
|
+
' return ok(value)',
|
|
305
251
|
' } catch (thrown) {',
|
|
306
252
|
' const early = stopped(thrown)',
|
|
307
253
|
' if (early !== undefined) {',
|
|
308
|
-
|
|
309
|
-
" return await settled(ok(early.value), 'stopped')",
|
|
254
|
+
' return ok(early[\'value\'])',
|
|
310
255
|
' }',
|
|
311
256
|
'',
|
|
312
257
|
' const message = thrown instanceof Error ? thrown.message : String(thrown)',
|
|
313
258
|
'',
|
|
314
259
|
" if (named(thrown, 'VersionConflictError')) {",
|
|
315
|
-
" return
|
|
260
|
+
" return fail('invalid', message, 'version_conflict', CONFLICT)",
|
|
316
261
|
' }',
|
|
317
262
|
'',
|
|
318
263
|
" const code: ResultCode = named(thrown, 'ValidationError') ? 'invalid' : 'error'",
|
|
319
264
|
' const because = platform(thrown) ?? message',
|
|
320
|
-
' return
|
|
321
|
-
' } finally {',
|
|
322
|
-
' span?.end()',
|
|
265
|
+
' return fail(code, message, because)',
|
|
323
266
|
' }',
|
|
324
267
|
'}',
|
|
325
268
|
'',
|