@pithy-sh/core 0.7.4 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/naming/domains.d.ts +13 -0
- package/dist/naming/domains.d.ts.map +1 -1
- package/dist/naming/domains.js +22 -1
- package/dist/seed/devLogin.d.ts +33 -0
- package/dist/seed/devLogin.d.ts.map +1 -1
- package/dist/seed/devLogin.js +28 -1
- package/dist/seed/seed.d.ts +25 -0
- package/dist/seed/seed.d.ts.map +1 -1
- package/dist/version.generated.d.ts +1 -1
- package/dist/version.generated.js +1 -1
- package/dist/workflow/faults.d.ts +34 -2
- package/dist/workflow/faults.d.ts.map +1 -1
- package/dist/workflow/faults.js +42 -18
- package/package.json +1 -1
- package/src/naming/domains.ts +23 -1
- package/src/seed/devLogin.ts +31 -0
- package/src/seed/seed.ts +25 -0
- package/src/version.generated.ts +1 -1
- package/src/workflow/faults.ts +61 -4
package/dist/naming/domains.d.ts
CHANGED
|
@@ -9,6 +9,19 @@ import { type AmbientEnv } from "../env/ambient";
|
|
|
9
9
|
*
|
|
10
10
|
* The one test for "is this a host" that an origin read from a stamp and an origin typed on a command line
|
|
11
11
|
* both pass, so the two cannot accept different things.
|
|
12
|
+
*
|
|
13
|
+
* **It answers the same on every runtime, and that is why the punycode rule is stated here.** It used to be
|
|
14
|
+
* borrowed: `seedHostOrigin` asked `new URL` to parse the host and took a throw as the answer, and an invalid
|
|
15
|
+
* A-label like `xn--a.test` was refused because the parser refused it. Node 24.20.0 bumped Ada from 3.4.4 to
|
|
16
|
+
* 4.0.0, Ada 4 stopped rejecting invalid punycode, and the same `pithy seed --host` began succeeding on Node 24
|
|
17
|
+
* while still failing on Node 22 — one command, two answers, decided by the adopter's Node. A rule borrowed from
|
|
18
|
+
* a parser changes under you, so this owns it.
|
|
19
|
+
*
|
|
20
|
+
* **Every A-label is refused, valid or not.** Telling a good one from a bad one is UTS-46, which needs IDNA
|
|
21
|
+
* mapping tables this package is not carrying into a Worker for the sake of a hostname check — and
|
|
22
|
+
* {@link HOSTNAME_PATTERN} is ASCII-only anyway, so a Unicode domain never reached here to begin with. This
|
|
23
|
+
* closes the hand-encoded spelling, and internationalized domains are a feature with a specification behind
|
|
24
|
+
* them rather than a side effect of a parser version.
|
|
12
25
|
*/
|
|
13
26
|
export declare function isPublicHostname(hostname: string): boolean;
|
|
14
27
|
/** The environments a domain may be declared for — every managed one, never `dev`. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"domains.d.ts","sourceRoot":"","sources":["../../src/naming/domains.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,KAAK,UAAU,EAAc,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"domains.d.ts","sourceRoot":"","sources":["../../src/naming/domains.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,KAAK,UAAU,EAAc,MAAM,gBAAgB,CAAC;AAgD7D;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAE1D;AAED,sFAAsF;AACtF,eAAO,MAAM,mBAAmB,wBAA8D,CAAC;AAE/F,uDAAuD;AACvD,eAAO,MAAM,YAAY;;;iBA+BrB,CAAC;AACL,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,CAAC;AAExD;;;;;GAKG;AACH,eAAO,MAAM,aAAa;;;;;;;;;iBAOvB,CAAC;AACJ,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAC;AAE1D;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,aAAa,GAAG,SAAS,EAAE,WAAW,EAAE,MAAM,GAAG,SAAS,GAAG,YAAY,GAAG,IAAI,CAKlH;AAED,gHAAgH;AAChH,wBAAgB,UAAU,CAAC,MAAM,EAAE,YAAY,GAAG,MAAM,CAEvD;AAED;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,YAAY,qBAAqB,CAAC;AAE/C,yFAAyF;AACzF,MAAM,WAAW,cAAc;IAC7B,qEAAqE;IACrE,MAAM,EAAE,MAAM,CAAC;IACf,iFAAiF;IACjF,QAAQ,EAAE,MAAM,CAAC;IACjB;;;OAGG;IACH,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAetG;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,aAAa,CAC3B,WAAW,EAAE,MAAM,GAAG,SAAS,EAC/B,OAAO,EAAE,aAAa,GAAG,SAAS,EAClC,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,GAC3B,cAAc,CAShB;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,SAAS,CACvB,WAAW,EAAE,MAAM,GAAG,SAAS,EAC/B,OAAO,EAAE,aAAa,GAAG,SAAS,EAClC,GAAG,GAAE,UAAyB,GAC7B,MAAM,CAER"}
|
package/dist/naming/domains.js
CHANGED
|
@@ -39,15 +39,36 @@ import { z } from "zod";
|
|
|
39
39
|
/** A hostname a Worker answers on. No scheme, no path, no port — wrangler's `routes` pattern is a host. */
|
|
40
40
|
const HOSTNAME_PATTERN = /^(?!-)[a-z0-9-]{1,63}(?<!-)(\.(?!-)[a-z0-9-]{1,63}(?<!-))+$/;
|
|
41
41
|
/**
|
|
42
|
+
* **A label that is a punycode A-label — the ASCII spelling of an internationalized name.**
|
|
43
|
+
*
|
|
44
|
+
* Anchored to a label boundary, so it is `xn--a.test` and `example.xn--p1ai` and never `myxn--a.test`:
|
|
45
|
+
* a name that merely reads that way is an ordinary name, and refusing it would be this rule reaching
|
|
46
|
+
* past what it is about.
|
|
47
|
+
*/
|
|
48
|
+
const PUNYCODE_LABEL = /(^|\.)xn--/;
|
|
49
|
+
/**
|
|
42
50
|
* Whether a string is a public hostname: dot-separated DNS labels, at least two, lowercase. No scheme, no
|
|
43
51
|
* port, no trailing dot, and nothing a URL parser decoded into one — `%2e%2e` is `..` once parsed, which is an
|
|
44
52
|
* empty label rather than a host (#643). `localhost` is not one: it has a single label, and it is this machine.
|
|
45
53
|
*
|
|
46
54
|
* The one test for "is this a host" that an origin read from a stamp and an origin typed on a command line
|
|
47
55
|
* both pass, so the two cannot accept different things.
|
|
56
|
+
*
|
|
57
|
+
* **It answers the same on every runtime, and that is why the punycode rule is stated here.** It used to be
|
|
58
|
+
* borrowed: `seedHostOrigin` asked `new URL` to parse the host and took a throw as the answer, and an invalid
|
|
59
|
+
* A-label like `xn--a.test` was refused because the parser refused it. Node 24.20.0 bumped Ada from 3.4.4 to
|
|
60
|
+
* 4.0.0, Ada 4 stopped rejecting invalid punycode, and the same `pithy seed --host` began succeeding on Node 24
|
|
61
|
+
* while still failing on Node 22 — one command, two answers, decided by the adopter's Node. A rule borrowed from
|
|
62
|
+
* a parser changes under you, so this owns it.
|
|
63
|
+
*
|
|
64
|
+
* **Every A-label is refused, valid or not.** Telling a good one from a bad one is UTS-46, which needs IDNA
|
|
65
|
+
* mapping tables this package is not carrying into a Worker for the sake of a hostname check — and
|
|
66
|
+
* {@link HOSTNAME_PATTERN} is ASCII-only anyway, so a Unicode domain never reached here to begin with. This
|
|
67
|
+
* closes the hand-encoded spelling, and internationalized domains are a feature with a specification behind
|
|
68
|
+
* them rather than a side effect of a parser version.
|
|
48
69
|
*/
|
|
49
70
|
function isPublicHostname(hostname) {
|
|
50
|
-
return hostname.length <= 253 && HOSTNAME_PATTERN.test(hostname);
|
|
71
|
+
return hostname.length <= 253 && HOSTNAME_PATTERN.test(hostname) && !PUNYCODE_LABEL.test(hostname);
|
|
51
72
|
}
|
|
52
73
|
/** The environments a domain may be declared for — every managed one, never `dev`. */
|
|
53
74
|
const DOMAIN_ENVIRONMENTS = ENVIRONMENTS.filter((environment) => environment !== "dev");
|
package/dist/seed/devLogin.d.ts
CHANGED
|
@@ -56,4 +56,37 @@ export declare const DevLogin: z.ZodObject<{
|
|
|
56
56
|
expiresAt: z.ZodCodec<z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodDate]>, z.ZodDate>;
|
|
57
57
|
}, z.core.$strip>;
|
|
58
58
|
export type DevLogin = z.output<typeof DevLogin>;
|
|
59
|
+
/**
|
|
60
|
+
* Every seeded dev login, keyed by `userId` — what `logs/dev-login.json` holds.
|
|
61
|
+
*
|
|
62
|
+
* **One entry per user the seed creates — `#667`.** It used to hold exactly one, named by a per-machine
|
|
63
|
+
* `dev.json`, so being a second seeded user meant reseeding or minting a claim by hand. Both are slow
|
|
64
|
+
* enough that people stop doing it, and the screens only one user can reach stop getting looked at. A
|
|
65
|
+
* claim is a signature over a user id and an expiry, so N of them cost no extra seeded rows and a few
|
|
66
|
+
* hundred bytes each.
|
|
67
|
+
*
|
|
68
|
+
* The key is the `userId`, which each entry also carries: the key is how the record is addressed and the
|
|
69
|
+
* field is what an entry means on its own, once something has picked one out of here.
|
|
70
|
+
*
|
|
71
|
+
* **Order is the file's, and for integer-like ids the language's.** `pithy dev` offers the identities in the
|
|
72
|
+
* order they are read, which is insertion order — except that JS enumerates canonical array-index keys
|
|
73
|
+
* first, in ascending numeric order, so a project whose user ids are stringified integers gets them
|
|
74
|
+
* numerically however the seed wrote them. Nothing depends on the order being the seed's; it is an offer
|
|
75
|
+
* order, not an identity.
|
|
76
|
+
*
|
|
77
|
+
* **The whole file is still one live credential, and the three properties that made one acceptable are
|
|
78
|
+
* what make N acceptable** — `logs/` is gitignored by the starter template, the artifact is written
|
|
79
|
+
* `0600`, and the writer refuses a symlink at the target.
|
|
80
|
+
*
|
|
81
|
+
* **Nothing migrates.** The single-entry file this replaced has strings where an entry belongs, so it does
|
|
82
|
+
* not parse, and every reader already answers "no dev login" to a file that does not — the banner stays
|
|
83
|
+
* quiet until the next `pithy seed`. The artifact is gitignored and regenerated, so that is the plan.
|
|
84
|
+
*/
|
|
85
|
+
export declare const DevLogins: z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
86
|
+
email: z.ZodString;
|
|
87
|
+
userId: z.ZodString;
|
|
88
|
+
claim: z.ZodString;
|
|
89
|
+
expiresAt: z.ZodCodec<z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodDate]>, z.ZodDate>;
|
|
90
|
+
}, z.core.$strip>>;
|
|
91
|
+
export type DevLogins = z.output<typeof DevLogins>;
|
|
59
92
|
//# sourceMappingURL=devLogin.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"devLogin.d.ts","sourceRoot":"","sources":["../../src/seed/devLogin.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB;;;;;;;;;;;;;;;;;;GAkBG;AAEH,2GAA2G;AAC3G,eAAO,MAAM,iBAAiB,SAAS,CAAC;AAExC,gHAAgH;AAChH,eAAO,MAAM,cAAc,mBAAmB,CAAC;AAE/C,uGAAuG;AACvG,eAAO,MAAM,cAAc,wBAA2C,CAAC;AAEvE;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,eAAe,uBAAuB,CAAC;AAEpD,uGAAuG;AACvG,eAAO,MAAM,qBAAqB,MAAM,CAAC;AAEzC;;;GAGG;AACH,eAAO,MAAM,QAAQ;;;;;iBAWqF,CAAC;AAC3G,MAAM,MAAM,QAAQ,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,QAAQ,CAAC,CAAC"}
|
|
1
|
+
{"version":3,"file":"devLogin.d.ts","sourceRoot":"","sources":["../../src/seed/devLogin.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB;;;;;;;;;;;;;;;;;;GAkBG;AAEH,2GAA2G;AAC3G,eAAO,MAAM,iBAAiB,SAAS,CAAC;AAExC,gHAAgH;AAChH,eAAO,MAAM,cAAc,mBAAmB,CAAC;AAE/C,uGAAuG;AACvG,eAAO,MAAM,cAAc,wBAA2C,CAAC;AAEvE;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,eAAe,uBAAuB,CAAC;AAEpD,uGAAuG;AACvG,eAAO,MAAM,qBAAqB,MAAM,CAAC;AAEzC;;;GAGG;AACH,eAAO,MAAM,QAAQ;;;;;iBAWqF,CAAC;AAC3G,MAAM,MAAM,QAAQ,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,QAAQ,CAAC,CAAC;AAEjD;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,eAAO,MAAM,SAAS;;;;;kBAEuE,CAAC;AAC9F,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,SAAS,CAAC,CAAC"}
|
package/dist/seed/devLogin.js
CHANGED
|
@@ -57,5 +57,32 @@ const DevLogin = z.object({
|
|
|
57
57
|
claim: z.string().describe("The signed, URI-encoded claim naming the user to sign in as. A live credential for the local database — never logged, never committed, never in an error payload. **Not a session:** it names a user and the route mints a session against it, so revoking a session leaves it working."),
|
|
58
58
|
expiresAt: JsonDate.describe("When the claim stops being accepted. ISO-8601 text on disk; a `Date` in app code.")
|
|
59
59
|
}).describe("A seeded dev login: the signed claim for one seeded user, written to `logs/dev-login.json`.");
|
|
60
|
+
/**
|
|
61
|
+
* Every seeded dev login, keyed by `userId` — what `logs/dev-login.json` holds.
|
|
62
|
+
*
|
|
63
|
+
* **One entry per user the seed creates — `#667`.** It used to hold exactly one, named by a per-machine
|
|
64
|
+
* `dev.json`, so being a second seeded user meant reseeding or minting a claim by hand. Both are slow
|
|
65
|
+
* enough that people stop doing it, and the screens only one user can reach stop getting looked at. A
|
|
66
|
+
* claim is a signature over a user id and an expiry, so N of them cost no extra seeded rows and a few
|
|
67
|
+
* hundred bytes each.
|
|
68
|
+
*
|
|
69
|
+
* The key is the `userId`, which each entry also carries: the key is how the record is addressed and the
|
|
70
|
+
* field is what an entry means on its own, once something has picked one out of here.
|
|
71
|
+
*
|
|
72
|
+
* **Order is the file's, and for integer-like ids the language's.** `pithy dev` offers the identities in the
|
|
73
|
+
* order they are read, which is insertion order — except that JS enumerates canonical array-index keys
|
|
74
|
+
* first, in ascending numeric order, so a project whose user ids are stringified integers gets them
|
|
75
|
+
* numerically however the seed wrote them. Nothing depends on the order being the seed's; it is an offer
|
|
76
|
+
* order, not an identity.
|
|
77
|
+
*
|
|
78
|
+
* **The whole file is still one live credential, and the three properties that made one acceptable are
|
|
79
|
+
* what make N acceptable** — `logs/` is gitignored by the starter template, the artifact is written
|
|
80
|
+
* `0600`, and the writer refuses a symlink at the target.
|
|
81
|
+
*
|
|
82
|
+
* **Nothing migrates.** The single-entry file this replaced has strings where an entry belongs, so it does
|
|
83
|
+
* not parse, and every reader already answers "no dev login" to a file that does not — the banner stays
|
|
84
|
+
* quiet until the next `pithy seed`. The artifact is gitignored and regenerated, so that is the plan.
|
|
85
|
+
*/
|
|
86
|
+
const DevLogins = z.record(z.string(), DevLogin).describe("Every seeded dev login, keyed by user id, as `logs/dev-login.json` holds them.");
|
|
60
87
|
//#endregion
|
|
61
|
-
export { DEV_LOGIN_CLAIM_PARAM, DEV_LOGIN_FILE, DEV_LOGIN_PATH, DEV_LOGIN_ROUTE, DevLogin, SEED_ARTIFACT_DIR };
|
|
88
|
+
export { DEV_LOGIN_CLAIM_PARAM, DEV_LOGIN_FILE, DEV_LOGIN_PATH, DEV_LOGIN_ROUTE, DevLogin, DevLogins, SEED_ARTIFACT_DIR };
|
package/dist/seed/seed.d.ts
CHANGED
|
@@ -178,6 +178,31 @@ export interface SeedPrepareContext {
|
|
|
178
178
|
* can write and never read.
|
|
179
179
|
*/
|
|
180
180
|
secret: (name: string) => Promise<string | undefined>;
|
|
181
|
+
/**
|
|
182
|
+
* The value **this run just minted** for a secret, or `undefined` — never what the environment holds.
|
|
183
|
+
*
|
|
184
|
+
* `pithy provision` creates a random value for every `cf-secrets-store` secret the registry declares
|
|
185
|
+
* mintable, writes it into the account's Secrets Store, and lets it go. The Secrets Store is write-only
|
|
186
|
+
* from the CLI, so that write is the only moment the value exists anywhere the toolchain can see it —
|
|
187
|
+
* and provisioning migrates and seeds in the same process, a few steps later. A fixture that must
|
|
188
|
+
* *seal* something at creation time (encrypt a private key under the environment's key-encryption
|
|
189
|
+
* secret, derive an id that the running app will re-derive) can only do it then. Afterwards the value
|
|
190
|
+
* is unrecoverable by design.
|
|
191
|
+
*
|
|
192
|
+
* **This is not {@link secret}, and the two are not interchangeable.** `secret` answers what the
|
|
193
|
+
* environment holds and refuses outside `dev`, absolutely (#159); that rule is untouched. This answers
|
|
194
|
+
* the strictly narrower question *did this run create one, a moment ago, in memory*. A set asking here
|
|
195
|
+
* for a secret this run did not mint gets `undefined` wherever it runs, in `dev` or anywhere else.
|
|
196
|
+
*
|
|
197
|
+
* **Synchronous, because it cannot fetch.** There is no file to open and no request to make; a promise
|
|
198
|
+
* in this signature would suggest otherwise.
|
|
199
|
+
*
|
|
200
|
+
* **Empty is ordinary.** A re-run mints nothing — absence is checked before anything is generated — and
|
|
201
|
+
* a standalone `pithy seed` mints nothing at all. Both answer `undefined` for every name, and neither
|
|
202
|
+
* is an error. A set that needs a minted value and is handed none says so itself; nothing is invented
|
|
203
|
+
* on its behalf.
|
|
204
|
+
*/
|
|
205
|
+
mintedThisRun: (name: string) => string | undefined;
|
|
181
206
|
/**
|
|
182
207
|
* The developer's machine-local preferences for this project, read from the Pithy config directory
|
|
183
208
|
* (`~/.config/pithy/<project>/dev.json`, or `%APPDATA%\pithy\<project>\dev.json` on Windows), parsed but
|
package/dist/seed/seed.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"seed.d.ts","sourceRoot":"","sources":["../../src/seed/seed.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"seed.d.ts","sourceRoot":"","sources":["../../src/seed/seed.ts"],"names":[],"mappings":"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"}
|
|
@@ -3,5 +3,5 @@
|
|
|
3
3
|
/** This package's npm name — the join key against a release feed. */
|
|
4
4
|
export declare const PACKAGE_NAME = "@pithy-sh/core";
|
|
5
5
|
/** This package's version, stamped from its own package.json at generation time. */
|
|
6
|
-
export declare const PACKAGE_VERSION = "0.
|
|
6
|
+
export declare const PACKAGE_VERSION = "0.9.0";
|
|
7
7
|
//# sourceMappingURL=version.generated.d.ts.map
|
|
@@ -4,6 +4,6 @@
|
|
|
4
4
|
/** This package's npm name — the join key against a release feed. */
|
|
5
5
|
const PACKAGE_NAME = "@pithy-sh/core";
|
|
6
6
|
/** This package's version, stamped from its own package.json at generation time. */
|
|
7
|
-
const PACKAGE_VERSION = "0.
|
|
7
|
+
const PACKAGE_VERSION = "0.9.0";
|
|
8
8
|
//#endregion
|
|
9
9
|
export { PACKAGE_NAME, PACKAGE_VERSION };
|
|
@@ -65,10 +65,42 @@ export interface WorkflowFault {
|
|
|
65
65
|
* test rather than a claim — and so the same function decides in a test, in Miniflare, and in production.
|
|
66
66
|
*/
|
|
67
67
|
export declare function classifyWorkflowFault(error: unknown, policy: WorkflowRetryPolicy): WorkflowFault;
|
|
68
|
-
/**
|
|
68
|
+
/**
|
|
69
|
+
* The half of a Workflow step this kit uses: run a named body, or serve its journalled result.
|
|
70
|
+
*
|
|
71
|
+
* **`sleep` is optional here and required on what {@link classifiedSteps} hands back, and the asymmetry
|
|
72
|
+
* is the truth rather than a convenience.** The platform's own `WorkflowStep` always offers one. What
|
|
73
|
+
* does not are this kit's hand-written test doubles, which exist to exercise the classification and
|
|
74
|
+
* nothing else — and demanding a primitive the classifier never classifies would cost every one of them
|
|
75
|
+
* a line of noise. Handing an optional one back instead would push a null check into the one caller that
|
|
76
|
+
* genuinely pauses.
|
|
77
|
+
*/
|
|
69
78
|
export interface WorkflowStepLike {
|
|
70
79
|
/** Run a named step, or return its journalled result if this instance already completed it. */
|
|
71
80
|
do<T>(name: string, fn: () => Promise<T>): Promise<T>;
|
|
81
|
+
/**
|
|
82
|
+
* Pause the instance for `durationMs` and resume it afterwards — possibly in a fresh isolate, which
|
|
83
|
+
* is the whole reason a caller reaches for it rather than for a timer. Absent only on a test double.
|
|
84
|
+
*/
|
|
85
|
+
sleep?(name: string, durationMs: number): Promise<void>;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* A step runner that can pause — what {@link classifiedSteps} hands back, always.
|
|
89
|
+
*
|
|
90
|
+
* The durable pause is why this type exists rather than being a convenience over the one above. A `sleep`
|
|
91
|
+
* is journalled, so a wait survives the instance being evicted during it; a `setTimeout` inside one step
|
|
92
|
+
* burns that step's wall clock and dies with the instance holding it.
|
|
93
|
+
*
|
|
94
|
+
* **It was introduced on a stronger claim, and the claim did not survive contact (pithy-sh/pithy#647).** The
|
|
95
|
+
* argument was that a Secrets Store binding answers the same for its isolate's whole life, so only a resume
|
|
96
|
+
* into a fresh isolate could ever see a write land. Measured against a real store on 2026-09-25, that is
|
|
97
|
+
* false: a binding refreshes inside a live isolate roughly 1.3-1.5s after the REST write, proved by holding
|
|
98
|
+
* two isolates warm through a write and watching each serve the old value before it and the new one after,
|
|
99
|
+
* over 328 samples. The durability argument above is the true one and is sufficient on its own.
|
|
100
|
+
*/
|
|
101
|
+
export interface DurableStepLike extends WorkflowStepLike {
|
|
102
|
+
/** Pause the instance for `durationMs`, then resume. */
|
|
103
|
+
sleep(name: string, durationMs: number): Promise<void>;
|
|
72
104
|
}
|
|
73
105
|
/**
|
|
74
106
|
* The platform's terminal-error class — `NonRetryableError` from `cloudflare:workflows`, taken as a
|
|
@@ -95,5 +127,5 @@ export interface TerminalErrorConstructor {
|
|
|
95
127
|
* `runSendBatch`, `runAtRestKeyRotation`) takes this instead with no change: the classification is
|
|
96
128
|
* declared once, at the entrypoint, and every step below it inherits the decision.
|
|
97
129
|
*/
|
|
98
|
-
export declare function classifiedSteps(step: WorkflowStepLike, policy: WorkflowRetryPolicy, terminalError: TerminalErrorConstructor):
|
|
130
|
+
export declare function classifiedSteps(step: WorkflowStepLike, policy: WorkflowRetryPolicy, terminalError: TerminalErrorConstructor): DurableStepLike;
|
|
99
131
|
//# sourceMappingURL=faults.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"faults.d.ts","sourceRoot":"","sources":["../../src/workflow/faults.ts"],"names":[],"mappings":"AAOA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAEH,2FAA2F;AAC3F,MAAM,MAAM,wBAAwB,GAAG,OAAO,GAAG,UAAU,CAAC;AAE5D;;;;;;GAMG;AACH,MAAM,WAAW,mBAAmB;IAClC,iHAAiH;IACjH,UAAU,EAAE,MAAM,CAAC;IACnB;;;;OAIG;IACH,SAAS,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;CAC7C;AAED,2FAA2F;AAC3F,MAAM,WAAW,aAAa;IAC5B,sBAAsB;IACtB,WAAW,EAAE,wBAAwB,CAAC;IACtC,mFAAmF;IACnF,IAAI,EAAE,MAAM,CAAC;IACb,+EAA+E;IAC/E,MAAM,EAAE,MAAM,CAAC;CAChB;AAsDD;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,mBAAmB,GAAG,aAAa,CA0BhG;AAED
|
|
1
|
+
{"version":3,"file":"faults.d.ts","sourceRoot":"","sources":["../../src/workflow/faults.ts"],"names":[],"mappings":"AAOA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAEH,2FAA2F;AAC3F,MAAM,MAAM,wBAAwB,GAAG,OAAO,GAAG,UAAU,CAAC;AAE5D;;;;;;GAMG;AACH,MAAM,WAAW,mBAAmB;IAClC,iHAAiH;IACjH,UAAU,EAAE,MAAM,CAAC;IACnB;;;;OAIG;IACH,SAAS,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;CAC7C;AAED,2FAA2F;AAC3F,MAAM,WAAW,aAAa;IAC5B,sBAAsB;IACtB,WAAW,EAAE,wBAAwB,CAAC;IACtC,mFAAmF;IACnF,IAAI,EAAE,MAAM,CAAC;IACb,+EAA+E;IAC/E,MAAM,EAAE,MAAM,CAAC;CAChB;AAsDD;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,mBAAmB,GAAG,aAAa,CA0BhG;AAED;;;;;;;;;GASG;AACH,MAAM,WAAW,gBAAgB;IAC/B,+FAA+F;IAC/F,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IACtD;;;OAGG;IACH,KAAK,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACzD;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,eAAgB,SAAQ,gBAAgB;IACvD,wDAAwD;IACxD,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACxD;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,wBAAwB;IACvC,KAAK,OAAO,EAAE,MAAM,GAAG,KAAK,CAAC;CAC9B;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAC7B,IAAI,EAAE,gBAAgB,EACtB,MAAM,EAAE,mBAAmB,EAC3B,aAAa,EAAE,wBAAwB,GACtC,eAAe,CAmDjB"}
|
package/dist/workflow/faults.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// SPDX-FileCopyrightText: 2026 Pithy
|
|
2
2
|
// SPDX-License-Identifier: MIT
|
|
3
|
-
import { PithyError } from "../error/pithyError.js";
|
|
3
|
+
import { InternalError, PithyError } from "../error/pithyError.js";
|
|
4
4
|
import { d1TransientFault } from "../data/withD1Retry.js";
|
|
5
5
|
import { encodeWorkflowStepMessage } from "./stepMessage.js";
|
|
6
6
|
//#region src/workflow/faults.ts
|
|
@@ -91,23 +91,47 @@ function classifyWorkflowFault(error, policy) {
|
|
|
91
91
|
* declared once, at the entrypoint, and every step below it inherits the decision.
|
|
92
92
|
*/
|
|
93
93
|
function classifiedSteps(step, policy, terminalError) {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
94
|
+
const runner = {
|
|
95
|
+
do(name, fn) {
|
|
96
|
+
return step.do(name, async () => {
|
|
97
|
+
try {
|
|
98
|
+
return await fn();
|
|
99
|
+
} catch (error) {
|
|
100
|
+
const fault = classifyWorkflowFault(error, policy);
|
|
101
|
+
if (fault.disposition === "retry") throw error;
|
|
102
|
+
const terminal = new terminalError(encodeWorkflowStepMessage({
|
|
103
|
+
code: fault.code,
|
|
104
|
+
message: messageOf(error),
|
|
105
|
+
action: pithyActionOf(error)
|
|
106
|
+
}));
|
|
107
|
+
terminal.cause = error;
|
|
108
|
+
throw terminal;
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
},
|
|
112
|
+
/**
|
|
113
|
+
* Forwarded, and deliberately unclassified. A pause raises no fault of the capability's — the
|
|
114
|
+
* instance either resumes or it is gone — so there is nothing here for a policy to decide.
|
|
115
|
+
*
|
|
116
|
+
* A runner that cannot pause is refused, and the refusal is raised **through `do`** rather than
|
|
117
|
+
* thrown from here. A throw from here is a throw the engine never classifies, so it is free to
|
|
118
|
+
* re-drive the instance — and re-driving is the unbounded loop the caller is spending its sleeps to
|
|
119
|
+
* avoid. Going through `do` makes the refusal terminal on its first attempt, like every other
|
|
120
|
+
* answer that cannot change.
|
|
121
|
+
*/
|
|
122
|
+
sleep(name, durationMs) {
|
|
123
|
+
const pause = step.sleep;
|
|
124
|
+
if (pause === void 0) return runner.do(`${name}/cannot-pause`, async () => {
|
|
125
|
+
throw new InternalError({
|
|
126
|
+
message: "This Workflow step runner cannot pause.",
|
|
127
|
+
action: "Give the step runner a `sleep`; the platform's own `WorkflowStep` always has one.",
|
|
128
|
+
detail: `step '${name}' asked to pause ${durationMs}ms through a runner that declares no sleep`
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
return pause.call(step, name, durationMs);
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
return runner;
|
|
111
135
|
}
|
|
112
136
|
//#endregion
|
|
113
137
|
export { classifiedSteps, classifyWorkflowFault };
|
package/package.json
CHANGED
package/src/naming/domains.ts
CHANGED
|
@@ -41,6 +41,15 @@ import { ENVIRONMENTS, FEATURE_ENVIRONMENT } from "./environment";
|
|
|
41
41
|
/** A hostname a Worker answers on. No scheme, no path, no port — wrangler's `routes` pattern is a host. */
|
|
42
42
|
const HOSTNAME_PATTERN = /^(?!-)[a-z0-9-]{1,63}(?<!-)(\.(?!-)[a-z0-9-]{1,63}(?<!-))+$/;
|
|
43
43
|
|
|
44
|
+
/**
|
|
45
|
+
* **A label that is a punycode A-label — the ASCII spelling of an internationalized name.**
|
|
46
|
+
*
|
|
47
|
+
* Anchored to a label boundary, so it is `xn--a.test` and `example.xn--p1ai` and never `myxn--a.test`:
|
|
48
|
+
* a name that merely reads that way is an ordinary name, and refusing it would be this rule reaching
|
|
49
|
+
* past what it is about.
|
|
50
|
+
*/
|
|
51
|
+
const PUNYCODE_LABEL = /(^|\.)xn--/;
|
|
52
|
+
|
|
44
53
|
/**
|
|
45
54
|
* Whether a string is a public hostname: dot-separated DNS labels, at least two, lowercase. No scheme, no
|
|
46
55
|
* port, no trailing dot, and nothing a URL parser decoded into one — `%2e%2e` is `..` once parsed, which is an
|
|
@@ -48,9 +57,22 @@ const HOSTNAME_PATTERN = /^(?!-)[a-z0-9-]{1,63}(?<!-)(\.(?!-)[a-z0-9-]{1,63}(?<!
|
|
|
48
57
|
*
|
|
49
58
|
* The one test for "is this a host" that an origin read from a stamp and an origin typed on a command line
|
|
50
59
|
* both pass, so the two cannot accept different things.
|
|
60
|
+
*
|
|
61
|
+
* **It answers the same on every runtime, and that is why the punycode rule is stated here.** It used to be
|
|
62
|
+
* borrowed: `seedHostOrigin` asked `new URL` to parse the host and took a throw as the answer, and an invalid
|
|
63
|
+
* A-label like `xn--a.test` was refused because the parser refused it. Node 24.20.0 bumped Ada from 3.4.4 to
|
|
64
|
+
* 4.0.0, Ada 4 stopped rejecting invalid punycode, and the same `pithy seed --host` began succeeding on Node 24
|
|
65
|
+
* while still failing on Node 22 — one command, two answers, decided by the adopter's Node. A rule borrowed from
|
|
66
|
+
* a parser changes under you, so this owns it.
|
|
67
|
+
*
|
|
68
|
+
* **Every A-label is refused, valid or not.** Telling a good one from a bad one is UTS-46, which needs IDNA
|
|
69
|
+
* mapping tables this package is not carrying into a Worker for the sake of a hostname check — and
|
|
70
|
+
* {@link HOSTNAME_PATTERN} is ASCII-only anyway, so a Unicode domain never reached here to begin with. This
|
|
71
|
+
* closes the hand-encoded spelling, and internationalized domains are a feature with a specification behind
|
|
72
|
+
* them rather than a side effect of a parser version.
|
|
51
73
|
*/
|
|
52
74
|
export function isPublicHostname(hostname: string): boolean {
|
|
53
|
-
return hostname.length <= 253 && HOSTNAME_PATTERN.test(hostname);
|
|
75
|
+
return hostname.length <= 253 && HOSTNAME_PATTERN.test(hostname) && !PUNYCODE_LABEL.test(hostname);
|
|
54
76
|
}
|
|
55
77
|
|
|
56
78
|
/** The environments a domain may be declared for — every managed one, never `dev`. */
|
package/src/seed/devLogin.ts
CHANGED
|
@@ -71,3 +71,34 @@ export const DevLogin = z
|
|
|
71
71
|
})
|
|
72
72
|
.describe("A seeded dev login: the signed claim for one seeded user, written to `logs/dev-login.json`.");
|
|
73
73
|
export type DevLogin = z.output<typeof DevLogin>;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Every seeded dev login, keyed by `userId` — what `logs/dev-login.json` holds.
|
|
77
|
+
*
|
|
78
|
+
* **One entry per user the seed creates — `#667`.** It used to hold exactly one, named by a per-machine
|
|
79
|
+
* `dev.json`, so being a second seeded user meant reseeding or minting a claim by hand. Both are slow
|
|
80
|
+
* enough that people stop doing it, and the screens only one user can reach stop getting looked at. A
|
|
81
|
+
* claim is a signature over a user id and an expiry, so N of them cost no extra seeded rows and a few
|
|
82
|
+
* hundred bytes each.
|
|
83
|
+
*
|
|
84
|
+
* The key is the `userId`, which each entry also carries: the key is how the record is addressed and the
|
|
85
|
+
* field is what an entry means on its own, once something has picked one out of here.
|
|
86
|
+
*
|
|
87
|
+
* **Order is the file's, and for integer-like ids the language's.** `pithy dev` offers the identities in the
|
|
88
|
+
* order they are read, which is insertion order — except that JS enumerates canonical array-index keys
|
|
89
|
+
* first, in ascending numeric order, so a project whose user ids are stringified integers gets them
|
|
90
|
+
* numerically however the seed wrote them. Nothing depends on the order being the seed's; it is an offer
|
|
91
|
+
* order, not an identity.
|
|
92
|
+
*
|
|
93
|
+
* **The whole file is still one live credential, and the three properties that made one acceptable are
|
|
94
|
+
* what make N acceptable** — `logs/` is gitignored by the starter template, the artifact is written
|
|
95
|
+
* `0600`, and the writer refuses a symlink at the target.
|
|
96
|
+
*
|
|
97
|
+
* **Nothing migrates.** The single-entry file this replaced has strings where an entry belongs, so it does
|
|
98
|
+
* not parse, and every reader already answers "no dev login" to a file that does not — the banner stays
|
|
99
|
+
* quiet until the next `pithy seed`. The artifact is gitignored and regenerated, so that is the plan.
|
|
100
|
+
*/
|
|
101
|
+
export const DevLogins = z
|
|
102
|
+
.record(z.string(), DevLogin)
|
|
103
|
+
.describe("Every seeded dev login, keyed by user id, as `logs/dev-login.json` holds them.");
|
|
104
|
+
export type DevLogins = z.output<typeof DevLogins>;
|
package/src/seed/seed.ts
CHANGED
|
@@ -196,6 +196,31 @@ export interface SeedPrepareContext {
|
|
|
196
196
|
* can write and never read.
|
|
197
197
|
*/
|
|
198
198
|
secret: (name: string) => Promise<string | undefined>;
|
|
199
|
+
/**
|
|
200
|
+
* The value **this run just minted** for a secret, or `undefined` — never what the environment holds.
|
|
201
|
+
*
|
|
202
|
+
* `pithy provision` creates a random value for every `cf-secrets-store` secret the registry declares
|
|
203
|
+
* mintable, writes it into the account's Secrets Store, and lets it go. The Secrets Store is write-only
|
|
204
|
+
* from the CLI, so that write is the only moment the value exists anywhere the toolchain can see it —
|
|
205
|
+
* and provisioning migrates and seeds in the same process, a few steps later. A fixture that must
|
|
206
|
+
* *seal* something at creation time (encrypt a private key under the environment's key-encryption
|
|
207
|
+
* secret, derive an id that the running app will re-derive) can only do it then. Afterwards the value
|
|
208
|
+
* is unrecoverable by design.
|
|
209
|
+
*
|
|
210
|
+
* **This is not {@link secret}, and the two are not interchangeable.** `secret` answers what the
|
|
211
|
+
* environment holds and refuses outside `dev`, absolutely (#159); that rule is untouched. This answers
|
|
212
|
+
* the strictly narrower question *did this run create one, a moment ago, in memory*. A set asking here
|
|
213
|
+
* for a secret this run did not mint gets `undefined` wherever it runs, in `dev` or anywhere else.
|
|
214
|
+
*
|
|
215
|
+
* **Synchronous, because it cannot fetch.** There is no file to open and no request to make; a promise
|
|
216
|
+
* in this signature would suggest otherwise.
|
|
217
|
+
*
|
|
218
|
+
* **Empty is ordinary.** A re-run mints nothing — absence is checked before anything is generated — and
|
|
219
|
+
* a standalone `pithy seed` mints nothing at all. Both answer `undefined` for every name, and neither
|
|
220
|
+
* is an error. A set that needs a minted value and is handed none says so itself; nothing is invented
|
|
221
|
+
* on its behalf.
|
|
222
|
+
*/
|
|
223
|
+
mintedThisRun: (name: string) => string | undefined;
|
|
199
224
|
/**
|
|
200
225
|
* The developer's machine-local preferences for this project, read from the Pithy config directory
|
|
201
226
|
* (`~/.config/pithy/<project>/dev.json`, or `%APPDATA%\pithy\<project>\dev.json` on Windows), parsed but
|
package/src/version.generated.ts
CHANGED
package/src/workflow/faults.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// SPDX-License-Identifier: MIT
|
|
3
3
|
|
|
4
4
|
import { d1TransientFault } from "../data/withD1Retry";
|
|
5
|
-
import { PithyError } from "../error/pithyError";
|
|
5
|
+
import { InternalError, PithyError } from "../error/pithyError";
|
|
6
6
|
import { encodeWorkflowStepMessage } from "./stepMessage";
|
|
7
7
|
|
|
8
8
|
/**
|
|
@@ -153,10 +153,43 @@ export function classifyWorkflowFault(error: unknown, policy: WorkflowRetryPolic
|
|
|
153
153
|
};
|
|
154
154
|
}
|
|
155
155
|
|
|
156
|
-
/**
|
|
156
|
+
/**
|
|
157
|
+
* The half of a Workflow step this kit uses: run a named body, or serve its journalled result.
|
|
158
|
+
*
|
|
159
|
+
* **`sleep` is optional here and required on what {@link classifiedSteps} hands back, and the asymmetry
|
|
160
|
+
* is the truth rather than a convenience.** The platform's own `WorkflowStep` always offers one. What
|
|
161
|
+
* does not are this kit's hand-written test doubles, which exist to exercise the classification and
|
|
162
|
+
* nothing else — and demanding a primitive the classifier never classifies would cost every one of them
|
|
163
|
+
* a line of noise. Handing an optional one back instead would push a null check into the one caller that
|
|
164
|
+
* genuinely pauses.
|
|
165
|
+
*/
|
|
157
166
|
export interface WorkflowStepLike {
|
|
158
167
|
/** Run a named step, or return its journalled result if this instance already completed it. */
|
|
159
168
|
do<T>(name: string, fn: () => Promise<T>): Promise<T>;
|
|
169
|
+
/**
|
|
170
|
+
* Pause the instance for `durationMs` and resume it afterwards — possibly in a fresh isolate, which
|
|
171
|
+
* is the whole reason a caller reaches for it rather than for a timer. Absent only on a test double.
|
|
172
|
+
*/
|
|
173
|
+
sleep?(name: string, durationMs: number): Promise<void>;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* A step runner that can pause — what {@link classifiedSteps} hands back, always.
|
|
178
|
+
*
|
|
179
|
+
* The durable pause is why this type exists rather than being a convenience over the one above. A `sleep`
|
|
180
|
+
* is journalled, so a wait survives the instance being evicted during it; a `setTimeout` inside one step
|
|
181
|
+
* burns that step's wall clock and dies with the instance holding it.
|
|
182
|
+
*
|
|
183
|
+
* **It was introduced on a stronger claim, and the claim did not survive contact (pithy-sh/pithy#647).** The
|
|
184
|
+
* argument was that a Secrets Store binding answers the same for its isolate's whole life, so only a resume
|
|
185
|
+
* into a fresh isolate could ever see a write land. Measured against a real store on 2026-09-25, that is
|
|
186
|
+
* false: a binding refreshes inside a live isolate roughly 1.3-1.5s after the REST write, proved by holding
|
|
187
|
+
* two isolates warm through a write and watching each serve the old value before it and the new one after,
|
|
188
|
+
* over 328 samples. The durability argument above is the true one and is sufficient on its own.
|
|
189
|
+
*/
|
|
190
|
+
export interface DurableStepLike extends WorkflowStepLike {
|
|
191
|
+
/** Pause the instance for `durationMs`, then resume. */
|
|
192
|
+
sleep(name: string, durationMs: number): Promise<void>;
|
|
160
193
|
}
|
|
161
194
|
|
|
162
195
|
/**
|
|
@@ -189,8 +222,8 @@ export function classifiedSteps(
|
|
|
189
222
|
step: WorkflowStepLike,
|
|
190
223
|
policy: WorkflowRetryPolicy,
|
|
191
224
|
terminalError: TerminalErrorConstructor,
|
|
192
|
-
):
|
|
193
|
-
|
|
225
|
+
): DurableStepLike {
|
|
226
|
+
const runner: DurableStepLike = {
|
|
194
227
|
do<T>(name: string, fn: () => Promise<T>): Promise<T> {
|
|
195
228
|
return step.do(name, async () => {
|
|
196
229
|
try {
|
|
@@ -215,5 +248,29 @@ export function classifiedSteps(
|
|
|
215
248
|
}
|
|
216
249
|
});
|
|
217
250
|
},
|
|
251
|
+
/**
|
|
252
|
+
* Forwarded, and deliberately unclassified. A pause raises no fault of the capability's — the
|
|
253
|
+
* instance either resumes or it is gone — so there is nothing here for a policy to decide.
|
|
254
|
+
*
|
|
255
|
+
* A runner that cannot pause is refused, and the refusal is raised **through `do`** rather than
|
|
256
|
+
* thrown from here. A throw from here is a throw the engine never classifies, so it is free to
|
|
257
|
+
* re-drive the instance — and re-driving is the unbounded loop the caller is spending its sleeps to
|
|
258
|
+
* avoid. Going through `do` makes the refusal terminal on its first attempt, like every other
|
|
259
|
+
* answer that cannot change.
|
|
260
|
+
*/
|
|
261
|
+
sleep(name: string, durationMs: number): Promise<void> {
|
|
262
|
+
const pause = step.sleep;
|
|
263
|
+
if (pause === undefined) {
|
|
264
|
+
return runner.do<void>(`${name}/cannot-pause`, async () => {
|
|
265
|
+
throw new InternalError({
|
|
266
|
+
message: "This Workflow step runner cannot pause.",
|
|
267
|
+
action: "Give the step runner a `sleep`; the platform's own `WorkflowStep` always has one.",
|
|
268
|
+
detail: `step '${name}' asked to pause ${durationMs}ms through a runner that declares no sleep`,
|
|
269
|
+
});
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
return pause.call(step, name, durationMs);
|
|
273
|
+
},
|
|
218
274
|
};
|
|
275
|
+
return runner;
|
|
219
276
|
}
|