@toa.io/extensions.introspection 1.0.0-alpha.305 → 1.0.0-alpha.306

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.
Files changed (67) hide show
  1. package/components/introspection.edges/manifest.toa.yaml +4 -12
  2. package/components/introspection.edges/operations/merge.js +1 -4
  3. package/components/introspection.edges/source/merge.ts +0 -3
  4. package/components/introspection.edges/types/toa.d.ts +0 -12
  5. package/components/introspection.signals/manifest.toa.yaml +109 -0
  6. package/components/introspection.signals/operations/configuration.js +17 -0
  7. package/components/introspection.signals/source/configuration.ts +26 -0
  8. package/components/introspection.signals/tsconfig.json +10 -0
  9. package/components/introspection.signals/types/index.d.ts +3 -0
  10. package/components/introspection.signals/types/toa.d.ts +61 -0
  11. package/package.json +7 -7
  12. package/readme.md +7 -26
  13. package/transpiled/Factory.d.ts +11 -5
  14. package/transpiled/Factory.js +34 -37
  15. package/transpiled/Factory.js.map +1 -1
  16. package/transpiled/Halt.d.ts +72 -0
  17. package/transpiled/Halt.js +279 -0
  18. package/transpiled/Halt.js.map +1 -0
  19. package/transpiled/Reporter.d.ts +10 -0
  20. package/transpiled/Reporter.js +23 -7
  21. package/transpiled/Reporter.js.map +1 -1
  22. package/transpiled/index.d.ts +1 -1
  23. package/transpiled/model.d.ts +3 -10
  24. package/ui/dist/_app/immutable/assets/0.lZd8PDGB.css +2 -0
  25. package/ui/dist/_app/immutable/chunks/BYGx-tUu.js +6 -0
  26. package/ui/dist/_app/immutable/chunks/BmMhn2QK.js +1 -0
  27. package/ui/dist/_app/immutable/chunks/C7j91YQ-.js +6 -0
  28. package/ui/dist/_app/immutable/chunks/{Bkm7IFSz.js → CB6qHsyu.js} +1 -1
  29. package/ui/dist/_app/immutable/chunks/CBoY9kBO.js +2 -0
  30. package/ui/dist/_app/immutable/chunks/CMJYA4Le.js +1 -0
  31. package/ui/dist/_app/immutable/chunks/Cyx235dG.js +1 -0
  32. package/ui/dist/_app/immutable/chunks/DSlJ2Pi2.js +1 -0
  33. package/ui/dist/_app/immutable/chunks/K6eu_fpK.js +1 -0
  34. package/ui/dist/_app/immutable/chunks/X3sfW33Q.js +1 -0
  35. package/ui/dist/_app/immutable/chunks/sTot5bBi.js +3 -0
  36. package/ui/dist/_app/immutable/entry/app._nBtHSlX.js +2 -0
  37. package/ui/dist/_app/immutable/entry/start.BIK196Vd.js +1 -0
  38. package/ui/dist/_app/immutable/nodes/0.jrtR8xep.js +1 -0
  39. package/ui/dist/_app/immutable/nodes/1.DcxTn8Wv.js +1 -0
  40. package/ui/dist/_app/immutable/nodes/2.C0fg7g4t.js +8 -0
  41. package/ui/dist/_app/immutable/nodes/3.PPZqOKXF.js +1 -0
  42. package/ui/dist/_app/immutable/nodes/4.DtCrZa9O.js +1 -0
  43. package/ui/dist/_app/immutable/nodes/5.G5Augfai.js +1 -0
  44. package/ui/dist/_app/version.json +1 -1
  45. package/ui/dist/index.html +14 -14
  46. package/transpiled/sample.d.ts +0 -7
  47. package/transpiled/sample.js +0 -39
  48. package/transpiled/sample.js.map +0 -1
  49. package/ui/dist/_app/immutable/assets/0.DG1WW3yI.css +0 -2
  50. package/ui/dist/_app/immutable/chunks/0-u_iN6y.js +0 -1
  51. package/ui/dist/_app/immutable/chunks/1AWk4n87.js +0 -6
  52. package/ui/dist/_app/immutable/chunks/BIDHvTr8.js +0 -1
  53. package/ui/dist/_app/immutable/chunks/BQPEyVde.js +0 -1
  54. package/ui/dist/_app/immutable/chunks/CJXg8Ahs.js +0 -2
  55. package/ui/dist/_app/immutable/chunks/CXHbcmHx.js +0 -1
  56. package/ui/dist/_app/immutable/chunks/CcHVMucT.js +0 -6
  57. package/ui/dist/_app/immutable/chunks/DINmginf.js +0 -1
  58. package/ui/dist/_app/immutable/chunks/FNfabOeV.js +0 -1
  59. package/ui/dist/_app/immutable/chunks/PUjOYRcs.js +0 -3
  60. package/ui/dist/_app/immutable/entry/app.DnsDRBZU.js +0 -2
  61. package/ui/dist/_app/immutable/entry/start.CwPkfBe-.js +0 -1
  62. package/ui/dist/_app/immutable/nodes/0.BC5OkQW-.js +0 -1
  63. package/ui/dist/_app/immutable/nodes/1.CXab7BsT.js +0 -1
  64. package/ui/dist/_app/immutable/nodes/2.31hlSS9p.js +0 -8
  65. package/ui/dist/_app/immutable/nodes/3.zu-dF4dT.js +0 -1
  66. package/ui/dist/_app/immutable/nodes/4.QGYaIsvq.js +0 -1
  67. 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, with `src`
5
- # saying what caused it — another operation, an event, or a service. What the
6
- # application declares is described by `introspection.nodes`; an edge is what
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,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibWVyZ2UuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zb3VyY2UvbWVyZ2UudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBRUE7Ozs7OztHQU1HO0FBQ0gsTUFBTSxVQUFVLFVBQVUsQ0FBQyxLQUFpQixFQUFFLE9BQWlCO0lBQzdELEtBQUssTUFBTSxJQUFJLElBQUksT0FBTyxFQUFFLENBQUM7UUFDM0IsTUFBTSxRQUFRLEdBQUcsS0FBSyxDQUFDLEtBQUssQ0FBQyxJQUFJLENBQUMsRUFBRSxDQUFDLENBQUE7UUFFckMsSUFBSSxRQUFRLEtBQUssU0FBUztZQUFFLFNBQVE7UUFFcEMsSUFBSSxDQUFDLEdBQUcsR0FBRyxRQUFRLENBQUMsR0FBRyxDQUFBO1FBQ3ZCLElBQUksQ0FBQyxHQUFHLEdBQUcsUUFBUSxDQUFDLEdBQUcsQ0FBQTtRQUV2QiwrREFBK0Q7UUFDL0QsSUFBSSxRQUFRLENBQUMsTUFBTSxLQUFLLFNBQVM7WUFBRSxJQUFJLENBQUMsTUFBTSxHQUFHLFFBQVEsQ0FBQyxNQUFNLENBQUE7SUFDbEUsQ0FBQztJQUVELE9BQU8sT0FBTyxDQUFBO0FBQ2hCLENBQUMifQ==
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,10 @@
1
+ {
2
+ "extends": "../../../../tsconfig.component.json",
3
+ "compilerOptions": {
4
+ "outDir": "./operations",
5
+ "rootDir": "./source"
6
+ },
7
+ "include": [
8
+ "source"
9
+ ]
10
+ }
@@ -0,0 +1,3 @@
1
+ export * from './toa.d.ts'
2
+
3
+ // What a manifest does not state belongs here, and every run keeps it.
@@ -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.305",
3
+ "version": "1.0.0-alpha.306",
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.305",
31
- "@toa.io/definitions": "1.0.0-alpha.305",
30
+ "@toa.io/core": "1.0.0-alpha.306",
31
+ "@toa.io/definitions": "1.0.0-alpha.306",
32
32
  "@toa.io/generic": "1.0.0-alpha.304",
33
- "@toa.io/norm": "1.0.0-alpha.305",
33
+ "@toa.io/norm": "1.0.0-alpha.306",
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 @samples and not @shutdown' && TOA_INTROSPECTION_SAMPLES=1 TSX_TSCONFIG_PATH=features/steps/tsconfig.json NODE_OPTIONS=--import=tsx cucumber-js --tags 'not @skip and @samples' && TOA_INTROSPECTION_INTERVAL=3600 TSX_TSCONFIG_PATH=features/steps/tsconfig.json NODE_OPTIONS=--import=tsx cucumber-js --tags 'not @skip and @shutdown'"
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": "6a28476303826e2484cc2ea6306efee6f19fb594"
43
+ "gitHead": "0693d180a8053a7d07dca879dac2a3c4784d6d45"
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
- Sampling can be prohibited for an individual component in `manifest.toa.yaml`:
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
@@ -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 settings;
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 settings are warm.
17
- * A component booted on its own (without a composition) falls back to
18
- * the environment, with sampling off.
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 resolve;
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,
@@ -1,50 +1,38 @@
1
1
  import { Connector } from '@toa.io/core';
2
- import { DISABLED, environment, component as declaration, settings } from '@toa.io/definitions/extensions.introspection';
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
- settings = {};
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 resolved = settings(locator.namespace, declaration(decl), this.options);
22
- this.settings[locator.id] = resolved;
23
- if (!resolved.enabled || locator.namespace === NAMESPACE)
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
- const resolved = this.resolve(locator);
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
- reply = await invoke(endpoint, request);
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
- const sample = resolved.samples && samplable(request?.input)
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
- const composition = new Composition(this.host);
70
- const explorer = new Explorer();
71
- explorer.depends(composition);
72
- if (this.options.ui)
73
- explorer.depends(new UI(uiPort()));
74
- return explorer;
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 settings are warm.
78
- * A component booted on its own (without a composition) falls back to
79
- * the environment, with sampling off.
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
- resolve(locator) {
79
+ observed(locator) {
82
80
  if (locator.namespace === NAMESPACE)
83
- return DISABLED;
84
- return (this.settings[locator.id] ??
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,EACL,QAAQ,EACR,WAAW,EACX,SAAS,IAAI,WAAW,EACxB,QAAQ,EACT,MAAM,8CAA8C,CAAA;AACrD,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,8CAA8C,CAAA;AAChF,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AACxC,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AACxC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACpC,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAC9C,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AACxC,OAAO,EAAE,EAAE,EAAE,MAAM,SAAS,CAAA;AAC5B,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAWhD,MAAM,OAAO,OAAO;IACD,IAAI,CAAM;IACV,OAAO,CAAgB;IACvB,QAAQ,GAA6B,EAAE,CAAA;IAChD,QAAQ,GAAoB,IAAI,CAAA;IAExC,YAAmB,IAAU;QAC3B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAA;QAChB,IAAI,CAAC,OAAO,GAAG,WAAW,EAAE,CAAA;IAC9B,CAAC;IAEM,MAAM,CACX,OAAgB,EAChB,IAAwB,EACxB,QAAkB;QAElB,MAAM,QAAQ,GAAG,QAAQ,CAAC,OAAO,CAAC,SAAU,EAAE,WAAW,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC,OAAO,CAAC,CAAA;QAE9E,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,GAAG,QAAQ,CAAA;QAEpC,IAAI,CAAC,QAAQ,CAAC,OAAO,IAAI,OAAO,CAAC,SAAS,KAAK,SAAS;YAAE,OAAO,IAAI,SAAS,EAAE,CAAA;QAEhF,OAAO,IAAI,MAAM,CAAC,IAAI,CAAC,SAAS,EAAE,EAAE,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAA;IACzD,CAAC;IAEM,SAAS,CAAC,SAAoB;QACnC,MAAM,OAAO,GAAG,SAAS,CAAC,OAAO,CAAA;QACjC,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,CAAA;QAEtC,IAAI,CAAC,QAAQ,CAAC,OAAO;YAAE,OAAO,SAAS,CAAA;QAEvC,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,EAAE,CAAA;QACjC,MAAM,MAAM,GAAG,SAAS,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAA;QAE/C,SAAS,CAAC,MAAM,GAAG,KAAK,EAAE,QAAgB,EAAE,OAAgB,EAAgB,EAAE;YAC5E,IAAI,OAAO,GAAY,IAAI,CAAA;YAC3B,IAAI,KAAwB,CAAA;YAE5B,IAAI,CAAC;gBACH,KAAK,GAAG,MAAM,MAAM,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAA;gBAEvC,IAAI,KAAK,EAAE,SAAS,KAAK,SAAS;oBAAE,OAAO,GAAG,WAAW,CAAA;qBACpD,IAAI,KAAK,EAAE,KAAK,KAAK,SAAS;oBAAE,OAAO,GAAG,OAAO,CAAA;gBAEtD,OAAO,KAAK,CAAA;YACd,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,OAAO,GAAG,WAAW,CAAA;gBAErB,MAAM,KAAK,CAAA;YACb,CAAC;oBAAS,CAAC;gBACT,kEAAkE;gBAClE,MAAM,GAAG,GAAW,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,CAAA;gBAC3C,MAAM,GAAG,GAAW;oBAClB,SAAS,EAAE,OAAO,CAAC,SAAU;oBAC7B,SAAS,EAAE,OAAO,CAAC,IAAI;oBACvB,SAAS,EAAE,QAAQ;iBACpB,CAAA;gBAED,MAAM,MAAM,GACV,QAAQ,CAAC,OAAO,IAAI,SAAS,CAAC,OAAO,EAAE,KAAK,CAAC;oBAC3C,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE,KAAK,EAAE,OAAO,CAAC;oBAClC,CAAC,CAAC,SAAS,CAAA;gBAEf,QAAQ,CAAC,OAAO,CAAC,EAAE,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,CAAC,CAAA;YACxC,CAAC;QACH,CAAC,CAAA;QAED,SAAS,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAA;QAE3B,OAAO,SAAS,CAAA;IAClB,CAAC;IAEM,OAAO;QACZ,IAAI,IAAI,CAAC,OAAO,KAAK,IAAI;YAAE,OAAO,IAAI,CAAA;QAEtC,MAAM,WAAW,GAAG,IAAI,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QAC9C,MAAM,QAAQ,GAAG,IAAI,QAAQ,EAAE,CAAA;QAE/B,QAAQ,CAAC,OAAO,CAAC,WAAW,CAAC,CAAA;QAE7B,IAAI,IAAI,CAAC,OAAO,CAAC,EAAE;YAAE,QAAQ,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,MAAM,EAAE,CAAC,CAAC,CAAA;QAEvD,OAAO,QAAQ,CAAA;IACjB,CAAC;IAED;;;;OAIG;IACK,OAAO,CAAC,OAAgB;QAC9B,IAAI,OAAO,CAAC,SAAS,KAAK,SAAS;YAAE,OAAO,QAAQ,CAAA;QAEpD,OAAO,CACL,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;YACzB,QAAQ,CACN,OAAO,CAAC,SAAU,EAClB,EAAE,EACF,IAAI,CAAC,OAAO,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,GAAG,IAAI,CAAC,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,CACnE,CACF,CAAA;IACH,CAAC;IAED;;;;OAIG;IACK,SAAS;QACf,IAAI,IAAI,CAAC,QAAQ,KAAK,IAAI,IAAI,IAAI,CAAC,QAAQ,CAAC,QAAQ;YAClD,IAAI,CAAC,QAAQ,GAAG,IAAI,QAAQ,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,OAAQ,CAAC,CAAA;QAExD,OAAO,IAAI,CAAC,QAAQ,CAAA;IACtB,CAAC;CACF;AAED,MAAM,OAAO,GAAG,EAAE,OAAO,EAAE,SAAS,EAAW,CAAA;AAE/C;;;GAGG;AACH,SAAS,MAAM,CAAC,MAA0B;IACxC,IAAI,MAAM,KAAK,SAAS;QAAE,OAAO,OAAO,CAAA;IAExC,IAAI,SAAS,IAAI,MAAM;QAAE,OAAO,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,CAAA;IAE3D,OAAO,OAAO,IAAI,MAAM;QACtB,CAAC,CAAC,EAAE,SAAS,EAAE,MAAM,CAAC,SAAS,EAAE,SAAS,EAAE,MAAM,CAAC,SAAS,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE;QACnF,CAAC,CAAC;YACE,SAAS,EAAE,MAAM,CAAC,SAAS;YAC3B,SAAS,EAAE,MAAM,CAAC,SAAS;YAC3B,SAAS,EAAE,MAAM,CAAC,SAAS;SAC5B,CAAA;AACP,CAAC"}
1
+ {"version":3,"file":"Factory.js","sourceRoot":"","sources":["../source/Factory.ts"],"names":[],"mappings":"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"}