@toa.io/extensions.introspection 1.0.0-alpha.305 → 1.0.0-alpha.307
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/components/introspection.edges/manifest.toa.yaml +4 -12
- package/components/introspection.edges/operations/merge.js +1 -4
- package/components/introspection.edges/source/merge.ts +0 -3
- package/components/introspection.edges/types/toa.d.ts +0 -12
- package/components/introspection.signals/manifest.toa.yaml +109 -0
- package/components/introspection.signals/operations/configuration.js +17 -0
- package/components/introspection.signals/source/configuration.ts +26 -0
- package/components/introspection.signals/tsconfig.json +10 -0
- package/components/introspection.signals/types/index.d.ts +3 -0
- package/components/introspection.signals/types/toa.d.ts +61 -0
- package/package.json +7 -7
- package/readme.md +7 -26
- package/transpiled/Composition.js +12 -2
- package/transpiled/Composition.js.map +1 -1
- package/transpiled/Factory.d.ts +11 -5
- package/transpiled/Factory.js +34 -37
- package/transpiled/Factory.js.map +1 -1
- package/transpiled/Halt.d.ts +72 -0
- package/transpiled/Halt.js +279 -0
- package/transpiled/Halt.js.map +1 -0
- package/transpiled/Reporter.d.ts +10 -0
- package/transpiled/Reporter.js +23 -7
- package/transpiled/Reporter.js.map +1 -1
- package/transpiled/index.d.ts +1 -1
- package/transpiled/model.d.ts +3 -10
- package/ui/dist/_app/immutable/assets/0.lZd8PDGB.css +2 -0
- package/ui/dist/_app/immutable/chunks/2cMl4Q7X.js +1 -0
- package/ui/dist/_app/immutable/chunks/{Bkm7IFSz.js → BSpCQ_T2.js} +1 -1
- package/ui/dist/_app/immutable/chunks/Bz9y_Qt4.js +6 -0
- package/ui/dist/_app/immutable/chunks/CnNGqUzH.js +1 -0
- package/ui/dist/_app/immutable/chunks/Cst06-jL.js +1 -0
- package/ui/dist/_app/immutable/chunks/CuHo2gYU.js +2 -0
- package/ui/dist/_app/immutable/chunks/DSlJ2Pi2.js +1 -0
- package/ui/dist/_app/immutable/chunks/DVKqjwr0.js +1 -0
- package/ui/dist/_app/immutable/chunks/DVtRwJ-b.js +6 -0
- package/ui/dist/_app/immutable/chunks/DYLJQF2a.js +1 -0
- package/ui/dist/_app/immutable/chunks/sTot5bBi.js +3 -0
- package/ui/dist/_app/immutable/entry/app.Cveu8h7C.js +2 -0
- package/ui/dist/_app/immutable/entry/start.DyO_9JwH.js +1 -0
- package/ui/dist/_app/immutable/nodes/0.D9z7gMyC.js +1 -0
- package/ui/dist/_app/immutable/nodes/1.DSfSOv0P.js +1 -0
- package/ui/dist/_app/immutable/nodes/2.Ci-kXrZk.js +8 -0
- package/ui/dist/_app/immutable/nodes/3.BNq-rSJ7.js +1 -0
- package/ui/dist/_app/immutable/nodes/4.BQkhi3Re.js +1 -0
- package/ui/dist/_app/immutable/nodes/5.CUmJeyuJ.js +1 -0
- package/ui/dist/_app/version.json +1 -1
- package/ui/dist/index.html +14 -14
- package/transpiled/sample.d.ts +0 -7
- package/transpiled/sample.js +0 -39
- package/transpiled/sample.js.map +0 -1
- package/ui/dist/_app/immutable/assets/0.DG1WW3yI.css +0 -2
- package/ui/dist/_app/immutable/chunks/0-u_iN6y.js +0 -1
- package/ui/dist/_app/immutable/chunks/1AWk4n87.js +0 -6
- package/ui/dist/_app/immutable/chunks/BIDHvTr8.js +0 -1
- package/ui/dist/_app/immutable/chunks/BQPEyVde.js +0 -1
- package/ui/dist/_app/immutable/chunks/CJXg8Ahs.js +0 -2
- package/ui/dist/_app/immutable/chunks/CXHbcmHx.js +0 -1
- package/ui/dist/_app/immutable/chunks/CcHVMucT.js +0 -6
- package/ui/dist/_app/immutable/chunks/DINmginf.js +0 -1
- package/ui/dist/_app/immutable/chunks/FNfabOeV.js +0 -1
- package/ui/dist/_app/immutable/chunks/PUjOYRcs.js +0 -3
- package/ui/dist/_app/immutable/entry/app.DnsDRBZU.js +0 -2
- package/ui/dist/_app/immutable/entry/start.CwPkfBe-.js +0 -1
- package/ui/dist/_app/immutable/nodes/0.BC5OkQW-.js +0 -1
- package/ui/dist/_app/immutable/nodes/1.CXab7BsT.js +0 -1
- package/ui/dist/_app/immutable/nodes/2.31hlSS9p.js +0 -8
- package/ui/dist/_app/immutable/nodes/3.zu-dF4dT.js +0 -1
- package/ui/dist/_app/immutable/nodes/4.QGYaIsvq.js +0 -1
- package/ui/dist/_app/immutable/nodes/5.BXAcSBZY.js +0 -1
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
namespace: introspection
|
|
2
2
|
name: edges
|
|
3
3
|
|
|
4
|
-
# One document per edge of the map: a call one component made to another
|
|
5
|
-
# saying what caused it — another operation, an event, or a service. What the
|
|
6
|
-
#
|
|
7
|
-
# actually happened.
|
|
4
|
+
# One document per edge of the map: that a call one component made to another was made,
|
|
5
|
+
# with `src` saying what caused it — another operation, an event, or a service. What the
|
|
6
|
+
# call carried is not recorded. What the application declares is described by
|
|
7
|
+
# `introspection.nodes`; an edge is what actually happened.
|
|
8
8
|
#
|
|
9
9
|
# Every replica of a component derives the same id for the same edge, so `merge` is
|
|
10
10
|
# an upsert and `UPDATED` shows when the edge was last observed.
|
|
@@ -29,14 +29,6 @@ entity:
|
|
|
29
29
|
namespace: { type: string }
|
|
30
30
|
component: { type: string }
|
|
31
31
|
operation: { type: string }
|
|
32
|
-
sample:
|
|
33
|
-
description: The last call observed on this edge
|
|
34
|
-
type: object
|
|
35
|
-
required: [at, outcome]
|
|
36
|
-
properties:
|
|
37
|
-
at: { type: integer }
|
|
38
|
-
input: {}
|
|
39
|
-
outcome: { type: string }
|
|
40
32
|
required: [src, dst]
|
|
41
33
|
|
|
42
34
|
operations:
|
|
@@ -12,10 +12,7 @@ export function transition(input, objects) {
|
|
|
12
12
|
continue;
|
|
13
13
|
edge.src = observed.src;
|
|
14
14
|
edge.dst = observed.dst;
|
|
15
|
-
// absent when sampling is off, and the last one wins otherwise
|
|
16
|
-
if (observed.sample !== undefined)
|
|
17
|
-
edge.sample = observed.sample;
|
|
18
15
|
}
|
|
19
16
|
return objects;
|
|
20
17
|
}
|
|
21
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
18
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibWVyZ2UuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zb3VyY2UvbWVyZ2UudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBRUE7Ozs7OztHQU1HO0FBQ0gsTUFBTSxVQUFVLFVBQVUsQ0FBQyxLQUFpQixFQUFFLE9BQWlCO0lBQzdELEtBQUssTUFBTSxJQUFJLElBQUksT0FBTyxFQUFFLENBQUM7UUFDM0IsTUFBTSxRQUFRLEdBQUcsS0FBSyxDQUFDLEtBQUssQ0FBQyxJQUFJLENBQUMsRUFBRSxDQUFDLENBQUE7UUFFckMsSUFBSSxRQUFRLEtBQUssU0FBUztZQUFFLFNBQVE7UUFFcEMsSUFBSSxDQUFDLEdBQUcsR0FBRyxRQUFRLENBQUMsR0FBRyxDQUFBO1FBQ3ZCLElBQUksQ0FBQyxHQUFHLEdBQUcsUUFBUSxDQUFDLEdBQUcsQ0FBQTtJQUN6QixDQUFDO0lBRUQsT0FBTyxPQUFPLENBQUE7QUFDaEIsQ0FBQyJ9
|
|
@@ -15,9 +15,6 @@ export function transition(input: MergeInput, objects: Entity[]): Entity[] {
|
|
|
15
15
|
|
|
16
16
|
edge.src = observed.src
|
|
17
17
|
edge.dst = observed.dst
|
|
18
|
-
|
|
19
|
-
// absent when sampling is off, and the last one wins otherwise
|
|
20
|
-
if (observed.sample !== undefined) edge.sample = observed.sample
|
|
21
18
|
}
|
|
22
19
|
|
|
23
20
|
return objects
|
|
@@ -19,12 +19,6 @@ export interface Entity {
|
|
|
19
19
|
component: string
|
|
20
20
|
operation: string
|
|
21
21
|
}
|
|
22
|
-
/** The last call observed on this edge */
|
|
23
|
-
sample?: {
|
|
24
|
-
at: number
|
|
25
|
-
input?: unknown
|
|
26
|
-
outcome: string
|
|
27
|
-
}
|
|
28
22
|
id: string
|
|
29
23
|
VERSION: number
|
|
30
24
|
CREATED: number
|
|
@@ -50,12 +44,6 @@ export type MergeInput = {
|
|
|
50
44
|
component: string
|
|
51
45
|
operation: string
|
|
52
46
|
}
|
|
53
|
-
/** The last call observed on this edge */
|
|
54
|
-
sample?: {
|
|
55
|
-
at: number
|
|
56
|
-
input?: unknown
|
|
57
|
-
outcome: string
|
|
58
|
-
}
|
|
59
47
|
}>
|
|
60
48
|
}
|
|
61
49
|
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
namespace: introspection
|
|
2
|
+
name: signals
|
|
3
|
+
|
|
4
|
+
# What an operator tells every process of the deployment at once. A signal is a record and
|
|
5
|
+
# nothing more: creating one publishes the prototype's `created` event, which every process
|
|
6
|
+
# is subscribed to, and carrying it out is theirs. Nothing here knows anyone is listening.
|
|
7
|
+
#
|
|
8
|
+
# The records stay, because they are how a halt is legible after the fact: `CREATED` says
|
|
9
|
+
# when it was asked for and `seconds` how long it was for, so whether one is on is read off
|
|
10
|
+
# the record and never has to be retracted — which matters, because the write that would
|
|
11
|
+
# retract it is the one thing a halted deployment cannot do.
|
|
12
|
+
|
|
13
|
+
entity:
|
|
14
|
+
properties:
|
|
15
|
+
type:
|
|
16
|
+
description: What the processes are being told to do
|
|
17
|
+
type: string
|
|
18
|
+
enum: [halt, stop]
|
|
19
|
+
# what a halt may ask for is the deployment's own say, read off `bounds` and applied by
|
|
20
|
+
# every process that hears the signal, so what is stated here is only what a number is
|
|
21
|
+
seconds:
|
|
22
|
+
description: How long every process stays disconnected before it builds itself again
|
|
23
|
+
type: integer
|
|
24
|
+
minimum: 0
|
|
25
|
+
quiescence:
|
|
26
|
+
description: How long the deployment is given to go quiet before the map is read
|
|
27
|
+
type: integer
|
|
28
|
+
minimum: 0
|
|
29
|
+
grace:
|
|
30
|
+
description: How long a process that has found the deployment busy waits before it leaves
|
|
31
|
+
type: integer
|
|
32
|
+
minimum: 2
|
|
33
|
+
maximum: 120
|
|
34
|
+
signal:
|
|
35
|
+
description: The halt a stop answers
|
|
36
|
+
type: string
|
|
37
|
+
required:
|
|
38
|
+
- type
|
|
39
|
+
- seconds
|
|
40
|
+
- quiescence
|
|
41
|
+
|
|
42
|
+
operations:
|
|
43
|
+
# `query: false` is what makes this a new signal rather than a change to one. What a signal
|
|
44
|
+
# may say is the entity's own say on itself, so the bounds are stated once: a value that
|
|
45
|
+
# breaks them is refused by the record and not by the route.
|
|
46
|
+
create:
|
|
47
|
+
description: Tell every process of this deployment to do something.
|
|
48
|
+
query: false
|
|
49
|
+
forward: transit
|
|
50
|
+
input: &signal
|
|
51
|
+
type: object
|
|
52
|
+
properties:
|
|
53
|
+
type:
|
|
54
|
+
type: string
|
|
55
|
+
seconds:
|
|
56
|
+
type: integer
|
|
57
|
+
quiescence:
|
|
58
|
+
type: integer
|
|
59
|
+
grace:
|
|
60
|
+
type: integer
|
|
61
|
+
signal:
|
|
62
|
+
type: string
|
|
63
|
+
required:
|
|
64
|
+
- type
|
|
65
|
+
- seconds
|
|
66
|
+
- quiescence
|
|
67
|
+
transit:
|
|
68
|
+
concurrency: retry
|
|
69
|
+
input: *signal
|
|
70
|
+
# what the deployment is configured to accept, by signal — `halt` is the only one there is,
|
|
71
|
+
# and it is absent where the deployment does not take halts
|
|
72
|
+
configuration:
|
|
73
|
+
description: What a signal of this deployment may ask for, in seconds.
|
|
74
|
+
output:
|
|
75
|
+
type: object
|
|
76
|
+
properties:
|
|
77
|
+
halt:
|
|
78
|
+
type: object
|
|
79
|
+
properties:
|
|
80
|
+
duration: &range
|
|
81
|
+
type: array
|
|
82
|
+
items:
|
|
83
|
+
type: integer
|
|
84
|
+
minItems: 2
|
|
85
|
+
maxItems: 2
|
|
86
|
+
quiescence: *range
|
|
87
|
+
required:
|
|
88
|
+
- duration
|
|
89
|
+
- quiescence
|
|
90
|
+
|
|
91
|
+
# the map must not describe itself
|
|
92
|
+
introspection: false
|
|
93
|
+
|
|
94
|
+
exposition:
|
|
95
|
+
isolated: true
|
|
96
|
+
/:
|
|
97
|
+
# not `system:introspection`: reading the map and stopping the product are not one
|
|
98
|
+
# authority, and the map's role is already held by whoever draws it
|
|
99
|
+
auth:role: system:halt
|
|
100
|
+
io:output: true
|
|
101
|
+
|
|
102
|
+
# what a signal may ask for, so that whoever writes one asks for something that will be
|
|
103
|
+
# carried out as written rather than clamped on arrival. The records themselves are not
|
|
104
|
+
# read: a process is told by the event, and during a halt the gateway answers 503 anyway
|
|
105
|
+
GET: configuration
|
|
106
|
+
|
|
107
|
+
# the records are kept all the same — a halt needs no retraction, because `CREATED` and
|
|
108
|
+
# `seconds` answer whether one is on
|
|
109
|
+
POST: create
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { environment } from '@toa.io/definitions/extensions.introspection';
|
|
2
|
+
/**
|
|
3
|
+
* What a signal of this deployment may ask for, in seconds.
|
|
4
|
+
*
|
|
5
|
+
* It is what every process that hears one holds it to, read from the same annotation, so
|
|
6
|
+
* whoever writes a signal can ask for something that will be carried out as written rather
|
|
7
|
+
* than clamped on arrival. A deployment that does not take halts says nothing about them.
|
|
8
|
+
*/
|
|
9
|
+
export function computation() {
|
|
10
|
+
const options = environment();
|
|
11
|
+
if (options === null)
|
|
12
|
+
throw new Error('Introspection is not configured in this process');
|
|
13
|
+
if (!options.halt)
|
|
14
|
+
return {};
|
|
15
|
+
return { halt: { duration: options.duration, quiescence: options.quiescence } };
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY29uZmlndXJhdGlvbi5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3NvdXJjZS9jb25maWd1cmF0aW9uLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBRSxXQUFXLEVBQUUsTUFBTSw4Q0FBOEMsQ0FBQTtBQUcxRTs7Ozs7O0dBTUc7QUFDSCxNQUFNLFVBQVUsV0FBVztJQUN6QixNQUFNLE9BQU8sR0FBRyxXQUFXLEVBQUUsQ0FBQTtJQUU3QixJQUFJLE9BQU8sS0FBSyxJQUFJO1FBQUUsTUFBTSxJQUFJLEtBQUssQ0FBQyxpREFBaUQsQ0FBQyxDQUFBO0lBRXhGLElBQUksQ0FBQyxPQUFPLENBQUMsSUFBSTtRQUFFLE9BQU8sRUFBRSxDQUFBO0lBRTVCLE9BQU8sRUFBRSxJQUFJLEVBQUUsRUFBRSxRQUFRLEVBQUUsT0FBTyxDQUFDLFFBQVEsRUFBRSxVQUFVLEVBQUUsT0FBTyxDQUFDLFVBQVUsRUFBRSxFQUFFLENBQUE7QUFDakYsQ0FBQyJ9
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { environment } from '@toa.io/definitions/extensions.introspection'
|
|
2
|
+
import type { Bounds } from '@toa.io/definitions/extensions.introspection'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* What a signal of this deployment may ask for, in seconds.
|
|
6
|
+
*
|
|
7
|
+
* It is what every process that hears one holds it to, read from the same annotation, so
|
|
8
|
+
* whoever writes a signal can ask for something that will be carried out as written rather
|
|
9
|
+
* than clamped on arrival. A deployment that does not take halts says nothing about them.
|
|
10
|
+
*/
|
|
11
|
+
export function computation(): Configuration {
|
|
12
|
+
const options = environment()
|
|
13
|
+
|
|
14
|
+
if (options === null) throw new Error('Introspection is not configured in this process')
|
|
15
|
+
|
|
16
|
+
if (!options.halt) return {}
|
|
17
|
+
|
|
18
|
+
return { halt: { duration: options.duration, quiescence: options.quiescence } }
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
interface Configuration {
|
|
22
|
+
halt?: {
|
|
23
|
+
duration: Bounds
|
|
24
|
+
quiescence: Bounds
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// Written by `toa types`. Every run rewrites it.
|
|
2
|
+
// What a manifest does not state belongs in a file of your own.
|
|
3
|
+
|
|
4
|
+
import type { Options, Query } from '@toa.io/core/types'
|
|
5
|
+
import type { Readable } from 'node:stream'
|
|
6
|
+
|
|
7
|
+
export interface Entity {
|
|
8
|
+
/** What the processes are being told to do */
|
|
9
|
+
type: "halt" | "stop"
|
|
10
|
+
/** How long every process stays disconnected before it builds itself again */
|
|
11
|
+
seconds: number
|
|
12
|
+
/** How long the deployment is given to go quiet before the map is read */
|
|
13
|
+
quiescence: number
|
|
14
|
+
/** How long a process that has found the deployment busy waits before it leaves */
|
|
15
|
+
grace?: number
|
|
16
|
+
/** The halt a stop answers */
|
|
17
|
+
signal?: string
|
|
18
|
+
id: string
|
|
19
|
+
VERSION: number
|
|
20
|
+
CREATED: number
|
|
21
|
+
UPDATED: number
|
|
22
|
+
DELETED: number | null
|
|
23
|
+
REGION: number
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export type CreateInput = {
|
|
27
|
+
type: string
|
|
28
|
+
seconds: number
|
|
29
|
+
quiescence: number
|
|
30
|
+
grace?: number
|
|
31
|
+
signal?: string
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export type TransitInput = {
|
|
35
|
+
type: string
|
|
36
|
+
seconds: number
|
|
37
|
+
quiescence: number
|
|
38
|
+
grace?: number
|
|
39
|
+
signal?: string
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export type ConfigurationOutput = {
|
|
43
|
+
halt?: {
|
|
44
|
+
duration: number[]
|
|
45
|
+
quiescence: number[]
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface Component {
|
|
50
|
+
/** Tell every process of this deployment to do something. */
|
|
51
|
+
create: (request: { input: CreateInput, task?: boolean }, options?: Options) => Promise<unknown>
|
|
52
|
+
transit: (request: { input: TransitInput, query?: Query<Entity>, task?: boolean }, options?: Options) => Promise<Entity>
|
|
53
|
+
/** What a signal of this deployment may ask for, in seconds. */
|
|
54
|
+
configuration: (request: { input?: null, task?: boolean }, options?: Options) => Promise<ConfigurationOutput>
|
|
55
|
+
assign: (request: { input?: null, query?: Query<Entity>, task?: boolean }, options?: Options) => Promise<Entity>
|
|
56
|
+
ensure: (request: { input?: null, query?: Query<Entity>, task?: boolean }, options?: Options) => Promise<Entity>
|
|
57
|
+
enumerate: (request: { input?: null, query?: Query<Entity>, task?: boolean }, options?: Options) => Promise<Entity[]>
|
|
58
|
+
observe: (request: { input?: null, query?: Query<Entity>, task?: boolean }, options?: Options) => Promise<Entity | null>
|
|
59
|
+
stream: (request: { input?: null, query?: Query<Entity>, task?: boolean }, options?: Options) => Promise<Readable>
|
|
60
|
+
terminate: (request: { input?: null, query?: Query<Entity>, task?: boolean }, options?: Options) => Promise<Entity>
|
|
61
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@toa.io/extensions.introspection",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.307",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Toa Introspection",
|
|
6
6
|
"author": "temich <tema.gurtovoy@gmail.com>",
|
|
@@ -27,18 +27,18 @@
|
|
|
27
27
|
"!**/.env*"
|
|
28
28
|
],
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@toa.io/core": "1.0.0-alpha.
|
|
31
|
-
"@toa.io/definitions": "1.0.0-alpha.
|
|
30
|
+
"@toa.io/core": "1.0.0-alpha.306",
|
|
31
|
+
"@toa.io/definitions": "1.0.0-alpha.307",
|
|
32
32
|
"@toa.io/generic": "1.0.0-alpha.304",
|
|
33
|
-
"@toa.io/norm": "1.0.0-alpha.
|
|
33
|
+
"@toa.io/norm": "1.0.0-alpha.307",
|
|
34
34
|
"@toa.io/schemas": "1.0.0-alpha.304",
|
|
35
35
|
"openspan": "1.0.0-alpha.305"
|
|
36
36
|
},
|
|
37
37
|
"scripts": {
|
|
38
|
-
"transpile": "npx tsc && npx tsc -p ./components/introspection.nodes && npx tsc -p ./components/introspection.edges",
|
|
38
|
+
"transpile": "npx tsc && npx tsc -p ./components/introspection.nodes && npx tsc -p ./components/introspection.edges && npx tsc -p ./components/introspection.signals",
|
|
39
39
|
"build:ui": "npm --prefix ui ci && npm --prefix ui run build",
|
|
40
40
|
"prepack": "npm run build:ui",
|
|
41
|
-
"features": "TSX_TSCONFIG_PATH=features/steps/tsconfig.json NODE_OPTIONS=--import=tsx cucumber-js --tags 'not @skip and not @
|
|
41
|
+
"features": "TSX_TSCONFIG_PATH=features/steps/tsconfig.json NODE_OPTIONS=--import=tsx cucumber-js --tags 'not @skip and not @shutdown' && TOA_INTROSPECTION_INTERVAL=3600 TSX_TSCONFIG_PATH=features/steps/tsconfig.json NODE_OPTIONS=--import=tsx cucumber-js --tags 'not @skip and @shutdown'"
|
|
42
42
|
},
|
|
43
|
-
"gitHead": "
|
|
43
|
+
"gitHead": "d5f4b2e97e315ba8ab657039d0407565c96e34bc"
|
|
44
44
|
}
|
package/readme.md
CHANGED
|
@@ -7,12 +7,14 @@ The topology includes:
|
|
|
7
7
|
|
|
8
8
|
- components and their entities, operations, events, and receivers;
|
|
9
9
|
- declared event relations between components;
|
|
10
|
-
- calls observed between components and services at runtime
|
|
11
|
-
- input and outcome samples for observed calls, when sampling is enabled.
|
|
10
|
+
- calls observed between components and services at runtime.
|
|
12
11
|
|
|
13
12
|
This provides a single view of both the product's declared structure and the communication paths
|
|
14
13
|
that are exercised at runtime. The topology is available through a web UI and an HTTP API.
|
|
15
14
|
|
|
15
|
+
An observed call is recorded as the pair it connects and nothing else: what the call carried is
|
|
16
|
+
not collected, and nothing an application passes between its components reaches the topology.
|
|
17
|
+
|
|
16
18
|
Introspection is enabled by default for every component in a composition.
|
|
17
19
|
|
|
18
20
|
## Configuration
|
|
@@ -21,20 +23,20 @@ Configure Introspection in `context.toa.yaml`:
|
|
|
21
23
|
|
|
22
24
|
```yaml
|
|
23
25
|
introspection:
|
|
24
|
-
samples: false
|
|
25
26
|
interval: 300
|
|
26
27
|
threshold: 1024
|
|
27
28
|
ui: true
|
|
29
|
+
halt: false
|
|
28
30
|
```
|
|
29
31
|
|
|
30
32
|
All properties are optional.
|
|
31
33
|
|
|
32
34
|
| Property | Default | Description |
|
|
33
35
|
| ----------- | ------- | --------------------------------------------------------------------------------------------- |
|
|
34
|
-
| `samples` | `false` | Enables collection of call input and outcome samples. |
|
|
35
36
|
| `interval` | `300` | Interval between topology updates, in seconds. |
|
|
36
37
|
| `threshold` | `1024` | Number of distinct observed interactions that triggers an update before the interval expires. |
|
|
37
38
|
| `ui` | `true` | Publishes the web UI. |
|
|
39
|
+
| `halt` | `false` | Accepts [halt](/documentation/halt.md) signals, which stop the whole deployment. `true`, or a pair of `[min, max]` bounds for what one may ask for: `duration` and `quiescence`, in seconds. |
|
|
38
40
|
| `resources` | — | Resource requirements for the Introspection deployment. |
|
|
39
41
|
|
|
40
42
|
Resource requirements can be declared specifically for Introspection:
|
|
@@ -60,33 +62,12 @@ does not use Exposition must disable Introspection.
|
|
|
60
62
|
|
|
61
63
|
### Component configuration
|
|
62
64
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
```yaml
|
|
66
|
-
introspection:
|
|
67
|
-
samples: false
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
This restriction cannot be overridden by the context configuration.
|
|
71
|
-
|
|
72
|
-
Exclude a component from the topology entirely:
|
|
65
|
+
Exclude a component from the topology in its `manifest.toa.yaml`:
|
|
73
66
|
|
|
74
67
|
```yaml
|
|
75
68
|
introspection: false
|
|
76
69
|
```
|
|
77
70
|
|
|
78
|
-
## Samples
|
|
79
|
-
|
|
80
|
-
A sample contains the input and outcome of an observed call. Sampling is disabled by default
|
|
81
|
-
because call inputs may contain production or personal data.
|
|
82
|
-
|
|
83
|
-
Samples are collected only when they are enabled in the context and not prohibited by the target
|
|
84
|
-
component. Payloads in the `identity` namespace are never collected. Properties whose names match
|
|
85
|
-
known secret patterns are redacted, streams are excluded, and oversized values are replaced with a
|
|
86
|
-
truncation marker.
|
|
87
|
-
|
|
88
|
-
Components that process sensitive data should disable sampling explicitly in their manifests.
|
|
89
|
-
|
|
90
71
|
## Web UI
|
|
91
72
|
|
|
92
73
|
The web UI visualizes the product topology and is enabled by default. It is published at
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { readdirSync } from 'node:fs';
|
|
2
2
|
import { resolve } from 'node:path';
|
|
3
3
|
import { Connector } from '@toa.io/core';
|
|
4
|
+
import { environment, NAMESPACE, SIGNALS } from '@toa.io/definitions/extensions.introspection';
|
|
4
5
|
/** Hosts the introspection components in the explorer process. */
|
|
5
6
|
export class Composition extends Connector {
|
|
6
7
|
host;
|
|
@@ -18,9 +19,17 @@ export class Composition extends Connector {
|
|
|
18
19
|
export function find() {
|
|
19
20
|
return entries().map((entry) => resolve(ROOT, entry.name));
|
|
20
21
|
}
|
|
22
|
+
/**
|
|
23
|
+
* The components this process hosts, which is what the deployment provisioned for it: a
|
|
24
|
+
* deployment that has not asked for halts is rendered nothing for the signals component — no
|
|
25
|
+
* storage address among the rest — so a process that hosted it would fail to boot on a pointer
|
|
26
|
+
* nobody wrote.
|
|
27
|
+
*/
|
|
21
28
|
function entries() {
|
|
22
|
-
const entries = readdirSync(ROOT, { withFileTypes: true });
|
|
23
|
-
|
|
29
|
+
const entries = readdirSync(ROOT, { withFileTypes: true }).filter((entry) => entry.isDirectory());
|
|
30
|
+
if (environment()?.halt === true)
|
|
31
|
+
return entries;
|
|
32
|
+
return entries.filter((entry) => entry.name !== SIGNALS_COMPONENT);
|
|
24
33
|
}
|
|
25
34
|
/** The components this extension ships, where they are, for the process that runs them. */
|
|
26
35
|
export function components() {
|
|
@@ -28,4 +37,5 @@ export function components() {
|
|
|
28
37
|
return { labels: entries().map((entry) => entry.name.replace('.', '-')), paths };
|
|
29
38
|
}
|
|
30
39
|
const ROOT = resolve(import.meta.dirname, '../components/');
|
|
40
|
+
const SIGNALS_COMPONENT = `${NAMESPACE}.${SIGNALS}`;
|
|
31
41
|
//# sourceMappingURL=Composition.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Composition.js","sourceRoot":"","sources":["../source/Composition.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAe,MAAM,SAAS,CAAA;AAClD,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AACnC,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;
|
|
1
|
+
{"version":3,"file":"Composition.js","sourceRoot":"","sources":["../source/Composition.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAe,MAAM,SAAS,CAAA;AAClD,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AACnC,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AACxC,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,8CAA8C,CAAA;AAG9F,kEAAkE;AAClE,MAAM,OAAO,WAAY,SAAQ,SAAS;IACvB,IAAI,CAAM;IAE3B,YAAmB,IAAU;QAC3B,KAAK,EAAE,CAAA;QACP,IAAI,CAAC,IAAI,GAAG,IAAI,CAAA;IAClB,CAAC;IAEkB,KAAK,CAAC,IAAI;QAC3B,MAAM,KAAK,GAAG,IAAI,EAAE,CAAA;QACpB,MAAM,WAAW,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,CAAA;QAEtD,MAAM,WAAW,CAAC,OAAO,EAAE,CAAA;QAE3B,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,CAAA;IAC3B,CAAC;CACF;AAED,MAAM,UAAU,IAAI;IAClB,OAAO,OAAO,EAAE,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC,CAAA;AAC5D,CAAC;AAED;;;;;GAKG;AACH,SAAS,OAAO;IACd,MAAM,OAAO,GAAG,WAAW,CAAC,IAAI,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAC1E,KAAK,CAAC,WAAW,EAAE,CACpB,CAAA;IAED,IAAI,WAAW,EAAE,EAAE,IAAI,KAAK,IAAI;QAAE,OAAO,OAAO,CAAA;IAEhD,OAAO,OAAO,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,KAAK,iBAAiB,CAAC,CAAA;AACpE,CAAC;AAED,2FAA2F;AAC3F,MAAM,UAAU,UAAU;IACxB,MAAM,KAAK,GAAG,IAAI,EAAE,CAAA;IAEpB,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,EAAE,KAAK,EAAE,CAAA;AAClF,CAAC;AAOD,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,IAAI,CAAC,OAAO,EAAE,gBAAgB,CAAC,CAAA;AAE3D,MAAM,iBAAiB,GAAG,GAAG,SAAS,IAAI,OAAO,EAAE,CAAA"}
|
package/transpiled/Factory.d.ts
CHANGED
|
@@ -6,18 +6,24 @@ import type { extensions } from '@toa.io/core/types';
|
|
|
6
6
|
export declare class Factory implements extensions.Factory {
|
|
7
7
|
private readonly host;
|
|
8
8
|
private readonly options;
|
|
9
|
-
private readonly
|
|
9
|
+
private readonly mapped;
|
|
10
10
|
private reporter;
|
|
11
11
|
constructor(host: Host);
|
|
12
12
|
tenant(locator: Locator, decl: Declaration | null, manifest: Manifest): Connector;
|
|
13
13
|
component(component: Component): Component;
|
|
14
|
+
/**
|
|
15
|
+
* What this extension keeps in every process of a deployment, whatever that process runs:
|
|
16
|
+
* the ear a halt arrives at. Behind a gate, so that a halted process holds nothing — the
|
|
17
|
+
* subscription included — and hears the next signal only once it is back.
|
|
18
|
+
*/
|
|
19
|
+
resident(host: Host): Connector | null;
|
|
14
20
|
service(): Connector | null;
|
|
15
21
|
/**
|
|
16
|
-
* `tenant()` runs before any component is created, so
|
|
17
|
-
* A component booted on its own (without a composition) falls back to
|
|
18
|
-
* the
|
|
22
|
+
* `tenant()` runs before any component is created, so what it decided is warm.
|
|
23
|
+
* A component booted on its own (without a composition) falls back to the
|
|
24
|
+
* environment: it is on the map wherever collection is configured at all.
|
|
19
25
|
*/
|
|
20
|
-
private
|
|
26
|
+
private observed;
|
|
21
27
|
/**
|
|
22
28
|
* One collector per process — and per build of its tree. A reporter that went with a tree
|
|
23
29
|
* that has been taken down holds remotes that are gone: it would answer that it is ready,
|
package/transpiled/Factory.js
CHANGED
|
@@ -1,50 +1,38 @@
|
|
|
1
1
|
import { Connector } from '@toa.io/core';
|
|
2
|
-
import {
|
|
2
|
+
import { environment } from '@toa.io/definitions/extensions.introspection';
|
|
3
3
|
import { NAMESPACE, uiPort } from '@toa.io/definitions/extensions.introspection';
|
|
4
4
|
import { describe } from './describe.js';
|
|
5
5
|
import { Reporter } from './Reporter.js';
|
|
6
6
|
import { Tenant } from './Tenant.js';
|
|
7
|
+
import { Halt } from './Halt.js';
|
|
7
8
|
import { Composition } from './Composition.js';
|
|
8
9
|
import { Explorer } from './Explorer.js';
|
|
9
10
|
import { UI } from './UI.js';
|
|
10
|
-
import { capture, samplable } from './sample.js';
|
|
11
11
|
export class Factory {
|
|
12
12
|
host;
|
|
13
13
|
options;
|
|
14
|
-
|
|
14
|
+
mapped = {};
|
|
15
15
|
reporter = null;
|
|
16
16
|
constructor(host) {
|
|
17
17
|
this.host = host;
|
|
18
18
|
this.options = environment();
|
|
19
19
|
}
|
|
20
20
|
tenant(locator, decl, manifest) {
|
|
21
|
-
const
|
|
22
|
-
this.
|
|
23
|
-
if (!
|
|
21
|
+
const mapped = this.options !== null && decl !== false;
|
|
22
|
+
this.mapped[locator.id] = mapped;
|
|
23
|
+
if (!mapped || locator.namespace === NAMESPACE)
|
|
24
24
|
return new Connector();
|
|
25
25
|
return new Tenant(this.collector(), describe(manifest));
|
|
26
26
|
}
|
|
27
27
|
component(component) {
|
|
28
28
|
const locator = component.locator;
|
|
29
|
-
|
|
30
|
-
if (!resolved.enabled)
|
|
29
|
+
if (!this.observed(locator))
|
|
31
30
|
return component;
|
|
32
31
|
const reporter = this.collector();
|
|
33
32
|
const invoke = component.invoke.bind(component);
|
|
34
33
|
component.invoke = async (endpoint, request) => {
|
|
35
|
-
let outcome = 'ok';
|
|
36
|
-
let reply;
|
|
37
34
|
try {
|
|
38
|
-
|
|
39
|
-
if (reply?.exception !== undefined)
|
|
40
|
-
outcome = 'exception';
|
|
41
|
-
else if (reply?.error !== undefined)
|
|
42
|
-
outcome = 'error';
|
|
43
|
-
return reply;
|
|
44
|
-
}
|
|
45
|
-
catch (error) {
|
|
46
|
-
outcome = 'exception';
|
|
47
|
-
throw error;
|
|
35
|
+
return await invoke(endpoint, request);
|
|
48
36
|
}
|
|
49
37
|
finally {
|
|
50
38
|
// a call that failed is still a connection between two components
|
|
@@ -54,35 +42,44 @@ export class Factory {
|
|
|
54
42
|
component: locator.name,
|
|
55
43
|
operation: endpoint
|
|
56
44
|
};
|
|
57
|
-
|
|
58
|
-
? capture(request?.input, outcome)
|
|
59
|
-
: undefined;
|
|
60
|
-
reporter.observe({ src, dst, sample });
|
|
45
|
+
reporter.observe({ src, dst });
|
|
61
46
|
}
|
|
62
47
|
};
|
|
63
48
|
component.depends(reporter);
|
|
64
49
|
return component;
|
|
65
50
|
}
|
|
51
|
+
/**
|
|
52
|
+
* What this extension keeps in every process of a deployment, whatever that process runs:
|
|
53
|
+
* the ear a halt arrives at. Behind a gate, so that a halted process holds nothing — the
|
|
54
|
+
* subscription included — and hears the next signal only once it is back.
|
|
55
|
+
*/
|
|
56
|
+
resident(host) {
|
|
57
|
+
if (this.options?.halt !== true)
|
|
58
|
+
return null;
|
|
59
|
+
return host.gate(async () => new Halt(host, this.options));
|
|
60
|
+
}
|
|
61
|
+
// a halt takes the whole of it, the UI with it: a map nobody is reporting to is stale
|
|
66
62
|
service() {
|
|
67
63
|
if (this.options === null)
|
|
68
64
|
return null;
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
65
|
+
return this.host.gate(async () => {
|
|
66
|
+
const composition = new Composition(this.host);
|
|
67
|
+
const explorer = new Explorer();
|
|
68
|
+
explorer.depends(composition);
|
|
69
|
+
if (this.options?.ui === true)
|
|
70
|
+
explorer.depends(new UI(uiPort()));
|
|
71
|
+
return explorer;
|
|
72
|
+
});
|
|
75
73
|
}
|
|
76
74
|
/**
|
|
77
|
-
* `tenant()` runs before any component is created, so
|
|
78
|
-
* A component booted on its own (without a composition) falls back to
|
|
79
|
-
* the
|
|
75
|
+
* `tenant()` runs before any component is created, so what it decided is warm.
|
|
76
|
+
* A component booted on its own (without a composition) falls back to the
|
|
77
|
+
* environment: it is on the map wherever collection is configured at all.
|
|
80
78
|
*/
|
|
81
|
-
|
|
79
|
+
observed(locator) {
|
|
82
80
|
if (locator.namespace === NAMESPACE)
|
|
83
|
-
return
|
|
84
|
-
return
|
|
85
|
-
settings(locator.namespace, {}, this.options === null ? null : { ...this.options, samples: false }));
|
|
81
|
+
return false;
|
|
82
|
+
return this.mapped[locator.id] ?? this.options !== null;
|
|
86
83
|
}
|
|
87
84
|
/**
|
|
88
85
|
* One collector per process — and per build of its tree. A reporter that went with a tree
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Factory.js","sourceRoot":"","sources":["../source/Factory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AACxC,OAAO,
|
|
1
|
+
{"version":3,"file":"Factory.js","sourceRoot":"","sources":["../source/Factory.ts"],"names":[],"mappings":"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"}
|