@cat-factory/kernel 0.329.0 → 0.331.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,5 @@
1
- import type { EnvironmentAddress, EnvironmentRouteAttempt, EnvironmentRouteProof, EnvironmentUnreachableReason } from '@cat-factory/contracts';
1
+ import type { EnvironmentRouteCandidate, EnvironmentRouteAttempt, EnvironmentRouteProof, EnvironmentUnreachableReason } from '@cat-factory/contracts';
2
+ import type { HostResolveOutcome } from '../ports/host-resolver.js';
2
3
  import type { RouteProbeOutcome, RouteProbeRequest } from '../ports/route-probe.js';
3
4
  /** How long one target gets before it counts as a route that does not carry. */
4
5
  export declare const ROUTE_PROBE_TIMEOUT_MS = 4000;
@@ -12,53 +13,121 @@ export declare const ROUTE_PROBE_TIMEOUT_MS = 4000;
12
13
  * 162ms, so the ceiling is well under a second of ordinary cost.
13
14
  */
14
15
  export declare const MAX_PROBED_ADDRESSES = 4;
16
+ /**
17
+ * How long one stated NAME gets to resolve before the lookup counts as one the platform could not
18
+ * complete.
19
+ *
20
+ * Much tighter than {@link ROUTE_PROBE_TIMEOUT_MS}, because the two wait on different things: a
21
+ * connect legitimately hangs against a route that does not carry, which is the finding, whereas a
22
+ * resolver that has not answered in two seconds is not about to.
23
+ */
24
+ export declare const HOST_RESOLVE_TIMEOUT_MS = 2000;
25
+ /**
26
+ * How many stated NAMES a proof will look up.
27
+ *
28
+ * Its own bound rather than a share of {@link MAX_PROBED_ADDRESSES}, because they cap different
29
+ * costs: that one caps sockets opened, this one caps lookups made, and one name can expand into
30
+ * several addresses so neither implies the other. The same four, sized for the same shape (an
31
+ * internal and a public balancer, with room for a second availability zone).
32
+ */
33
+ export declare const MAX_RESOLVED_HOSTS = 4;
34
+ /**
35
+ * The stated NAMES a proof will resolve, in the provider's order, deduplicated and bounded.
36
+ *
37
+ * Exported because the caller does the I/O and the plan consumes the answers, so both have to
38
+ * agree about exactly which names are in scope. Stated ONCE here and read twice rather than
39
+ * recomputed on each side: two copies of a bound is how a name beyond it comes to be reported as a
40
+ * name nothing could resolve.
41
+ */
42
+ export declare function planHostResolutions(candidates?: readonly EnvironmentRouteCandidate[]): string[];
15
43
  /**
16
44
  * One target a proof will try, in the order the proof will try it.
17
45
  *
18
- * A discriminated union rather than a dial target with a "skip me" flag, because the refused
19
- * member carries NO {@link RouteProbeRequest}: an address the platform will not dial must be
20
- * structurally undialable by whoever iterates this list, not merely marked. Handing out a request
21
- * beside a boolean is how the next caller opens the socket anyway.
46
+ * A discriminated union rather than a dial target with a "skip me" flag, because the second member
47
+ * carries NO {@link RouteProbeRequest}: a target the platform will not dial must be structurally
48
+ * undialable by whoever iterates this list, not merely marked. Handing out a request beside a
49
+ * boolean is how the next caller opens the socket anyway.
50
+ *
51
+ * ONE undialled member rather than one per cause, because the callers do the same thing with all
52
+ * of them (record the attempt, move on) and the cause they differ by is already the
53
+ * {@link EnvironmentUnreachableReason} each carries.
22
54
  */
23
55
  export type RouteProbeTarget =
24
- /** Open a socket to this. `address` is null when the target dials the URL's own name. */
56
+ /**
57
+ * Open a socket to this. `address` is null when the target dials the URL's own name, and
58
+ * `statedHost` is set when the address came from RESOLVING a stated name rather than from a
59
+ * stated address, so a proof can publish which candidate carried and not merely which literal.
60
+ */
25
61
  {
26
62
  kind: 'dial';
27
63
  request: RouteProbeRequest;
28
64
  address: string | null;
65
+ statedHost?: string;
29
66
  label: string;
30
67
  }
31
- /** RECORD this and dial nothing: a stated address {@link isBridgeableAddress} refuses. */
68
+ /**
69
+ * RECORD this and dial nothing: an address {@link isBridgeableAddress} refuses, a candidate
70
+ * naming no single target, a stated name that resolved nowhere, and a stated name this
71
+ * deployment has nothing to resolve with.
72
+ */
32
73
  | {
33
- kind: 'refused';
34
- address: string;
74
+ kind: 'undialled';
35
75
  label: string;
36
76
  reason: EnvironmentUnreachableReason;
77
+ detail?: string;
37
78
  };
79
+ /** What {@link planRouteProbes} needs beyond the candidate list itself. */
80
+ export interface RouteProbePlan {
81
+ /**
82
+ * What the platform's resolver answered for each name {@link planHostResolutions} named, keyed
83
+ * by that same normalized name.
84
+ *
85
+ * A name IN that plan with no entry here means nothing was wired to resolve it, which is an
86
+ * admission about the deployment and is recorded as one. A name beyond the plan's bound is not
87
+ * in scope and is passed over, exactly as an address beyond the dial bound is.
88
+ */
89
+ resolutions?: ReadonlyMap<string, HostResolveOutcome>;
90
+ timeoutMs?: number;
91
+ }
38
92
  /**
39
93
  * The targets a proof tries for one environment, in order: the URL's own name first, then each
40
- * stated address in the PROVIDER'S preference order.
41
- *
42
- * The name goes first because it is the answer that needs no bridge, and a deployment where it
43
- * works must not start paying for `--add-host` entries and the warm-pool evictions they cost. The
44
- * addresses keep the provider's order because the provider is the only thing that knows which of
45
- * its balancers is the one it wants used; the platform decides only which one CARRIED.
46
- *
47
- * **A stated address is dialled only if a bridge could NAME it.** The rule is
48
- * `isBridgeableAddress`, and applying it HERE rather than only at bridge-build time is the whole
49
- * safety property of the probe: `addresses` is provider-authored data, so without it the
50
- * orchestrator opens sockets wherever a manifest says and records the results on a row a workspace
51
- * can read back, which is a liveness oracle against the deployment's own private network. The
52
- * refusal costs nothing real either, because an address no bridge may name is an address no
53
- * container could be pointed at, so proving it would prove something unusable. Refused addresses
54
- * are RECORDED (`kind: 'refused'`) rather than dropped: a shortened list nobody is told about is
55
- * how a provider's bad address becomes an unexplained `name_unresolved`.
94
+ * stated candidate in the PROVIDER'S preference order, a stated NAME expanded IN PLACE into the
95
+ * addresses it resolved to.
96
+ *
97
+ * The URL's name goes first because it is the answer that needs no bridge, and a deployment where
98
+ * it works must not start paying for `--add-host` entries and the warm-pool evictions they cost.
99
+ * The candidates keep the provider's order because the provider is the only thing that knows which
100
+ * of its balancers is the one it wants used; the platform decides only which one CARRIED. A name is
101
+ * expanded in place rather than having its addresses appended, so that order still means something
102
+ * when a provider states a name and an address side by side.
103
+ *
104
+ * **Every dialled target is an address a bridge could NAME**, whether the provider stated it or the
105
+ * platform resolved it. The rule is `isBridgeableAddress`, and applying it HERE rather than only at
106
+ * bridge-build time is the whole safety property of the probe: candidates are provider-authored
107
+ * data, so without it the orchestrator opens sockets wherever a manifest says and records the
108
+ * results on a row a workspace can read back, which is a liveness oracle against the deployment's
109
+ * own private network. Resolving first and grading each answer is what keeps that property intact
110
+ * for a name: the destination a bridge is built from is still an IP the platform itself proved, and
111
+ * a name answering with something unbridgeable is refused per address.
112
+ *
113
+ * The refusal costs nothing real either, because an address no bridge may name is an address no
114
+ * container could be pointed at, so proving it would prove something unusable. Refused and
115
+ * unresolvable targets are RECORDED (`kind: 'undialled'`) rather than dropped: a shortened list
116
+ * nobody is told about is how a provider's bad candidate becomes an unexplained `name_unresolved`.
117
+ *
118
+ * **Every cap this plan applies reports what it passed over**, in one final `not_attempted`
119
+ * target. Three of them bite (names beyond {@link MAX_RESOLVED_HOSTS}, addresses beyond the dial
120
+ * budget, records beyond the recording budget) and each ends the list early, so without that
121
+ * report a proof over a longer list is a prefix presented as the whole thing. It is not a
122
+ * cosmetic omission: {@link reduceRouteProof} grades `not_reached` only when every attempt
123
+ * established something, and a silently shortened list is how the deployer comes to fail a frame
124
+ * on a verdict about candidates the platform never looked at.
56
125
  *
57
126
  * Empty when there is no host or port to dial, which the caller reads as `no_candidate`: an
58
127
  * environment with no URL was never going to be reached, and that is a different fact from one
59
128
  * that was tried and failed.
60
129
  */
61
- export declare function planRouteProbes(host: string | null | undefined, port: number | null | undefined, candidates?: readonly EnvironmentAddress[], timeoutMs?: number): RouteProbeTarget[];
130
+ export declare function planRouteProbes(host: string | null | undefined, port: number | null | undefined, candidates?: readonly EnvironmentRouteCandidate[], plan?: RouteProbePlan): RouteProbeTarget[];
62
131
  /**
63
132
  * Record one DIALLED attempt for the proof's log, whether it carried or not.
64
133
  *
@@ -70,10 +139,27 @@ export declare function planRouteProbes(host: string | null | undefined, port: n
70
139
  export declare function recordRouteAttempt(target: Extract<RouteProbeTarget, {
71
140
  kind: 'dial';
72
141
  }>, outcome: RouteProbeOutcome): EnvironmentRouteAttempt;
73
- /** Record a target the platform REFUSED to dial, so the omission is on the proof rather than lost. */
74
- export declare function recordRefusedAttempt(target: Extract<RouteProbeTarget, {
75
- kind: 'refused';
142
+ /**
143
+ * Record a target the platform never DIALLED, so the omission is on the proof rather than lost.
144
+ *
145
+ * Carries the same `detail` a dialled attempt does, because one of the causes that lands here says
146
+ * nothing on its own: a lookup that failed rather than answering nothing is `probe_failed`, and
147
+ * without the resolver's own words a reader cannot tell a DNS timeout from a resolver outage from a
148
+ * bug in the adapter.
149
+ */
150
+ export declare function recordUndialledAttempt(target: Extract<RouteProbeTarget, {
151
+ kind: 'undialled';
76
152
  }>): EnvironmentRouteAttempt;
153
+ /**
154
+ * The target that carried, as {@link reduceRouteProof} publishes it: the address, plus the stated
155
+ * NAME it was resolved from when it came from one.
156
+ *
157
+ * The dial target's own shape, so the caller passes what it already has rather than picking two
158
+ * fields apart. Null for a proof where nothing carried, where `via` is null for the other reason.
159
+ */
160
+ export type CarryingTarget = Pick<Extract<RouteProbeTarget, {
161
+ kind: 'dial';
162
+ }>, 'address' | 'statedHost'>;
77
163
  /**
78
164
  * Fold a completed set of attempts into the proof that is stored and narrated.
79
165
  *
@@ -82,16 +168,17 @@ export declare function recordRefusedAttempt(target: Extract<RouteProbeTarget, {
82
168
  *
83
169
  * - **`reached`** as soon as any attempt carried, publishing the target that did.
84
170
  * - **`inconclusive`** when nothing carried AND some attempt left a route unruled-out: a probe
85
- * that could not classify its own failure, or nothing to try at all. A workerd connect message
86
- * matching none of that facade's markers, or a Node errno outside the mapped five, arrives
87
- * here, and reading either as a verdict about the environment is how a diagnostic becomes a
88
- * second way for a healthy deploy to die. The reason names the attempt that left it unknown.
171
+ * that could not classify its own failure, a candidate the plan passed over, or nothing to try
172
+ * at all. A workerd connect message matching none of that facade's markers, or a Node errno
173
+ * outside the mapped five, arrives here, and reading either as a verdict about the environment
174
+ * is how a diagnostic becomes a second way for a healthy deploy to die. The reason names the
175
+ * attempt that left it unknown.
89
176
  * - **`not_reached`** only when EVERY attempt established something and none of them carried.
90
177
  * The reported reason is then the FIRST attempt's, which is always the name, so a reader is
91
178
  * told what happened to the address they were given rather than what happened to the last
92
179
  * balancer in someone's preference list. The attempt log carries the rest, in order.
93
180
  */
94
- export declare function reduceRouteProof(attempts: readonly EnvironmentRouteAttempt[], carriedVia: string | null, checkedAt: number): EnvironmentRouteProof;
181
+ export declare function reduceRouteProof(attempts: readonly EnvironmentRouteAttempt[], carried: CarryingTarget | null, checkedAt: number): EnvironmentRouteProof;
95
182
  /**
96
183
  * The operator-facing sentence for an environment nothing could reach, with every target tried.
97
184
  *
@@ -147,7 +234,7 @@ export declare function describeRouteTargets(attempts: readonly EnvironmentRoute
147
234
  * because it is an admission about the platform and naming it a cause is how "we could not tell"
148
235
  * comes to read as a verdict.
149
236
  */
150
- export declare function determinateRouteCause(candidates: readonly EnvironmentAddress[], proof: EnvironmentRouteProof | null): string | null;
237
+ export declare function determinateRouteCause(candidates: readonly EnvironmentRouteCandidate[], proof: EnvironmentRouteProof | null): string | null;
151
238
  /**
152
239
  * The proof recorded when nothing was wired to open a socket.
153
240
  *
@@ -1 +1 @@
1
- {"version":3,"file":"environment-reachability.logic.d.ts","sourceRoot":"","sources":["../../src/domain/environment-reachability.logic.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EACV,kBAAkB,EAClB,uBAAuB,EACvB,qBAAqB,EACrB,4BAA4B,EAC7B,MAAM,wBAAwB,CAAA;AAE/B,OAAO,KAAK,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAA;AAMnF,gFAAgF;AAChF,eAAO,MAAM,sBAAsB,OAAO,CAAA;AAE1C;;;;;;;;GAQG;AACH,eAAO,MAAM,oBAAoB,IAAI,CAAA;AAErC;;;;;;;GAOG;AACH,MAAM,MAAM,gBAAgB;AAC1B,yFAAyF;AACvF;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,iBAAiB,CAAC;IAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE;AACrF,0FAA0F;GACxF;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,4BAA4B,CAAA;CAAE,CAAA;AAE7F;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,eAAe,CAC7B,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAC/B,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAC/B,UAAU,GAAE,SAAS,kBAAkB,EAAO,EAC9C,SAAS,GAAE,MAA+B,GACzC,gBAAgB,EAAE,CA4BpB;AA8BD;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,OAAO,CAAC,gBAAgB,EAAE;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,EACnD,OAAO,EAAE,iBAAiB,GACzB,uBAAuB,CAQzB;AAED,sGAAsG;AACtG,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,OAAO,CAAC,gBAAgB,EAAE;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,CAAC,GACrD,uBAAuB,CAEzB;AAwCD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,SAAS,uBAAuB,EAAE,EAC5C,UAAU,EAAE,MAAM,GAAG,IAAI,EACzB,SAAS,EAAE,MAAM,GAChB,qBAAqB,CA6BvB;AAcD;;;;;;;;;GASG;AACH,wBAAgB,8BAA8B,CAC5C,GAAG,EAAE,MAAM,GAAG,IAAI,EAClB,KAAK,EAAE,qBAAqB,GAC3B,MAAM,CAKR;AAED;;;;;;;;;GASG;AACH,wBAAgB,yBAAyB,CACvC,GAAG,EAAE,MAAM,GAAG,IAAI,EAClB,KAAK,EAAE,qBAAqB,GAC3B,MAAM,CAKR;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,SAAS,uBAAuB,EAAE,GAAG,MAAM,CAOzF;AAQD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,qBAAqB,CACnC,UAAU,EAAE,SAAS,kBAAkB,EAAE,EACzC,KAAK,EAAE,qBAAqB,GAAG,IAAI,GAClC,MAAM,GAAG,IAAI,CAwCf;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,qBAAqB,CAEtE"}
1
+ {"version":3,"file":"environment-reachability.logic.d.ts","sourceRoot":"","sources":["../../src/domain/environment-reachability.logic.ts"],"names":[],"mappings":"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"}