@potential-guide/check-environment 0.0.0 → 0.1.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.
Files changed (99) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +213 -2
  3. package/dist/bundles/directories/resources/check.json +19 -0
  4. package/dist/bundles/directories/resources/references/en/check.md +18 -0
  5. package/dist/bundles/directories/resources/references/en/problems/directory-unusable.md +10 -0
  6. package/dist/bundles/directories/resources/references/en/problems/not-directory.md +7 -0
  7. package/dist/bundles/directories/schemas/0.json +51 -0
  8. package/dist/bundles/directories/schemas/1.json +74 -0
  9. package/dist/bundles/directories/schemas/2.json +4 -0
  10. package/dist/bundles/directories/task.yaml +22 -0
  11. package/dist/bundles/directories/worker.cjs +4 -0
  12. package/dist/bundles/directories.car +0 -0
  13. package/dist/bundles/network/resources/check.json +21 -0
  14. package/dist/bundles/network/resources/references/en/check.md +21 -0
  15. package/dist/bundles/network/resources/references/en/problems/dns-unavailable.md +8 -0
  16. package/dist/bundles/network/resources/references/en/problems/http-status.md +9 -0
  17. package/dist/bundles/network/resources/references/en/problems/http-unavailable.md +9 -0
  18. package/dist/bundles/network/schemas/0.json +76 -0
  19. package/dist/bundles/network/schemas/1.json +78 -0
  20. package/dist/bundles/network/schemas/2.json +4 -0
  21. package/dist/bundles/network/task.yaml +23 -0
  22. package/dist/bundles/network/worker.cjs +2 -0
  23. package/dist/bundles/network.car +0 -0
  24. package/dist/bundles/references.json +17 -0
  25. package/dist/bundles/tools/resources/check.json +23 -0
  26. package/dist/bundles/tools/resources/references/en/check.md +21 -0
  27. package/dist/bundles/tools/resources/references/en/problems/tool-missing.md +8 -0
  28. package/dist/bundles/tools/resources/references/en/problems/tool-unusable.md +9 -0
  29. package/dist/bundles/tools/resources/references/en/problems/tool-version.md +8 -0
  30. package/dist/bundles/tools/resources/references/en/problems/version-unparseable.md +8 -0
  31. package/dist/bundles/tools/schemas/0.json +50 -0
  32. package/dist/bundles/tools/schemas/1.json +82 -0
  33. package/dist/bundles/tools/schemas/2.json +4 -0
  34. package/dist/bundles/tools/task.yaml +24 -0
  35. package/dist/bundles/tools/worker.cjs +2 -0
  36. package/dist/bundles/tools.car +0 -0
  37. package/dist/command.d.ts +8 -0
  38. package/dist/command.d.ts.map +1 -0
  39. package/dist/command.js +105 -0
  40. package/dist/command.js.map +1 -0
  41. package/dist/contracts.d.ts +36 -0
  42. package/dist/contracts.d.ts.map +1 -0
  43. package/dist/contracts.js +14 -0
  44. package/dist/contracts.js.map +1 -0
  45. package/dist/definitions/directories.d.ts +1 -0
  46. package/dist/definitions/directories.js +1 -0
  47. package/dist/definitions/network.d.ts +1 -0
  48. package/dist/definitions/network.js +1 -0
  49. package/dist/definitions/tools.d.ts +1 -0
  50. package/dist/definitions/tools.js +1 -0
  51. package/dist/directories.d.ts +16 -0
  52. package/dist/directories.d.ts.map +1 -0
  53. package/dist/directories.js +65 -0
  54. package/dist/directories.js.map +1 -0
  55. package/dist/index.d.ts +5 -0
  56. package/dist/index.d.ts.map +1 -0
  57. package/dist/index.js +4 -0
  58. package/dist/index.js.map +1 -0
  59. package/dist/network.d.ts +18 -0
  60. package/dist/network.d.ts.map +1 -0
  61. package/dist/network.js +79 -0
  62. package/dist/network.js.map +1 -0
  63. package/dist/result.d.ts +20 -0
  64. package/dist/result.d.ts.map +1 -0
  65. package/dist/result.js +23 -0
  66. package/dist/result.js.map +1 -0
  67. package/dist/tasks/directories/contract.d.ts +21 -0
  68. package/dist/tasks/directories/contract.d.ts.map +1 -0
  69. package/dist/tasks/directories/contract.js +11 -0
  70. package/dist/tasks/directories/contract.js.map +1 -0
  71. package/dist/tasks/directories/main.d.ts +2 -0
  72. package/dist/tasks/directories/main.d.ts.map +1 -0
  73. package/dist/tasks/directories/main.js +7 -0
  74. package/dist/tasks/directories/main.js.map +1 -0
  75. package/dist/tasks/inputs.d.ts +6 -0
  76. package/dist/tasks/inputs.d.ts.map +1 -0
  77. package/dist/tasks/inputs.js +5 -0
  78. package/dist/tasks/inputs.js.map +1 -0
  79. package/dist/tasks/network/contract.d.ts +24 -0
  80. package/dist/tasks/network/contract.d.ts.map +1 -0
  81. package/dist/tasks/network/contract.js +25 -0
  82. package/dist/tasks/network/contract.js.map +1 -0
  83. package/dist/tasks/network/main.d.ts +2 -0
  84. package/dist/tasks/network/main.d.ts.map +1 -0
  85. package/dist/tasks/network/main.js +7 -0
  86. package/dist/tasks/network/main.js.map +1 -0
  87. package/dist/tasks/tools/contract.d.ts +23 -0
  88. package/dist/tasks/tools/contract.d.ts.map +1 -0
  89. package/dist/tasks/tools/contract.js +13 -0
  90. package/dist/tasks/tools/contract.js.map +1 -0
  91. package/dist/tasks/tools/main.d.ts +2 -0
  92. package/dist/tasks/tools/main.d.ts.map +1 -0
  93. package/dist/tasks/tools/main.js +7 -0
  94. package/dist/tasks/tools/main.js.map +1 -0
  95. package/dist/tools.d.ts +19 -0
  96. package/dist/tools.d.ts.map +1 -0
  97. package/dist/tools.js +49 -0
  98. package/dist/tools.js.map +1 -0
  99. package/package.json +44 -5
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 shazhou-ww
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,214 @@
1
- # @potential-guide/check-environment
1
+ # Environment check bundles
2
2
 
3
- This 0.0.0 release is a bootstrap placeholder. Use version 0.1.0 or newer.
3
+ Three reusable checks share a library and build into **three independent Task
4
+ Bundles**: `tools`, `directories`, and `network`. No Objective levels, repair
5
+ agents, package installation or long-running sampling are built into them.
6
+
7
+ ```powershell
8
+ pnpm --filter @potential-guide/check-environment... build
9
+ ```
10
+
11
+ The package contains `dist/bundles/<kind>/task.yaml`, the self-contained
12
+ `worker.cjs`, and the distributable `dist/bundles/<kind>.car`.
13
+ `dist/bundles/references.json` records each relative `directory`, `car` and
14
+ standard UnixFS root `cid`. The CAR and directory describe the same file tree
15
+ and root CID; there is no parallel Bundle digest. Build output is ignored by Git.
16
+
17
+ ## Standard author pipeline
18
+
19
+ The three `*.check.build.yaml` files are the only author declarations.
20
+ Their problemCodes generate the literal business types, runtime allowed-code
21
+ list, result Schema and Reference coverage matrix through
22
+ [`check-sdk/authoring`](../check-sdk/README.md).
23
+ The first stage snapshots source, generated code definitions and validated
24
+ Reference bytes into `dist/staging/<kind>/`, including a generated
25
+ `task.build.yaml` with explicit `runTask` entry and exact resource includes.
26
+ The second stage uses the ordinary Task Bundle `buildTask` API.
27
+ It reads only those fixed business inputs, not the live source or Reference
28
+ directories. No Check-specific compiler is added.
29
+
30
+ Before acceptance the orchestration compares CAR and directory CIDs and exact
31
+ resource bytes. All three candidates must succeed before `dist/bundles/` and
32
+ its final references.json are replaced together; failures retain accepted
33
+ artifacts and references. Staging and candidate outputs are not release inputs.
34
+
35
+ Each Bundle contains `schemas/`, `resources/check.json`, and only its own
36
+ `resources/references/en/check.md` plus `problems/<code>.md`. Read them through
37
+ `ipfs://<CID>/resources/references/en/check.md` or the resource index. Reference
38
+ frontmatter accepts only title and description (nonempty, description <=200
39
+ Unicode code points). Filename/language/problem coverage and exact bytes are
40
+ gated; Markdown bodies, sections and links are deliberately not validated.
41
+ Language completeness is declared per Check, with no automatic fallback.
42
+
43
+ ## Migration and entry points
44
+
45
+ The three Task Bundles are the execution entry points. The retired Check Host
46
+ is not restored, and no old Host command, registration or compatibility forwarding
47
+ is provided. Import/pin the emitted CAR and submit its root CID to the Engine;
48
+ do not submit an npm package name or an old Checker digest as a Bundle.
49
+
50
+ The library exports (`checkTools`, `checkDirectories`, `checkNetwork`, version
51
+ helpers and the directory write probe) remain shared implementations, not legacy
52
+ task entry points. Existing Objective Workers still consume them without child
53
+ Task scheduling. Removing them would break those consumers rather than migrate
54
+ an old runner. This scope does not migrate `pnpm check`, repository/package
55
+ standardization, devbox-health or setup/self-check Objectives.
56
+
57
+ ## Inputs
58
+
59
+ Tool versions (numeric dot-separated versions, not npm semver ranges):
60
+
61
+ ```json
62
+ {
63
+ "tools": [
64
+ {"command": "node", "args": ["--version"], "minimumVersion": "22.13.0"}
65
+ ],
66
+ "checkTimeoutMs": 5000
67
+ }
68
+ ```
69
+
70
+ Version output uses the same numeric version parser as devbox-health. Commands
71
+ run without a shell. On Windows, use a native executable or `node` with the
72
+ absolute JavaScript CLI entry in `args`, not `.cmd`/`.bat` shims. Tools remain
73
+ external prerequisites; the bundle does not install them. Tool launch, timeout,
74
+ output-limit and cleanup faults fail the Run rather than manufacture a gap.
75
+
76
+ Directories (absolute paths on the **execution machine**):
77
+
78
+ ```json
79
+ {
80
+ "directories": [
81
+ {"path": "D:\\Code\\demo", "requirements": ["exists", "read"]},
82
+ {"path": "D:\\TaskScratch", "requirements": ["exists", "write"]}
83
+ ]
84
+ }
85
+ ```
86
+
87
+ All entries must be existing directories, not final-component symbolic links.
88
+ `read` checks read/traversal access; `write` creates an exclusive temporary file,
89
+ reads it back and removes it, including on cancellation. No business directory
90
+ is created and no permissions are changed. Ownership and canonical-path
91
+ requirements remain Objective policy, not generic requirements.
92
+
93
+ DNS and HTTP:
94
+
95
+ ```json
96
+ {
97
+ "dns": ["localhost"],
98
+ "http": [
99
+ {"url": "http://127.0.0.1:8080/health", "expectedStatus": [200], "followRedirects": false}
100
+ ],
101
+ "checkTimeoutMs": 4000
102
+ }
103
+ ```
104
+
105
+ At least one DNS or HTTP check is required. Each endpoint has an independent
106
+ timeout. HTTP(S) URLs cannot embed credentials. Redirects are rejected unless
107
+ explicitly enabled. Responses are not downloaded; their bodies are cancelled.
108
+ DNS resolves in a bounded subprocess so cancellation cannot leave a native
109
+ lookup running inside the Worker. Timeout/cancellation is an execution fault,
110
+ not evidence that an endpoint is unavailable.
111
+
112
+ `checkTimeoutMs` is the per-command/endpoint business budget (default 5000ms).
113
+ The Task Request's separate `timeoutMs` controls task eligibility/lifetime and
114
+ is reserved by the Engine; it is not passed to the business callback.
115
+
116
+ Each list is bounded to 100 items. The message contract is TypeBox `Never`
117
+ (equivalent to JSON Schema `false`); the checks do not
118
+ accept business messages. Request/Run cancellation still works through the SDK.
119
+
120
+ ## Results and submission
121
+
122
+ A passing check returns exactly:
123
+
124
+ ```json
125
+ {"pass": true}
126
+ ```
127
+
128
+ A completed check with unmet requirements returns only actual problems:
129
+
130
+ ```json
131
+ {
132
+ "pass": false,
133
+ "problems": [
134
+ {"target": "node", "code": "tool-version", "actual": "20.0.0"}
135
+ ]
136
+ }
137
+ ```
138
+
139
+ Passing items are omitted. Each problem includes `target`, `code`, and nullable
140
+ `actual`. Failure results require a nonempty `problems` array; passing results
141
+ have no `problems` field. Neither variant includes `checks` or `ok`. Invocation
142
+ faults still fail the Run, not return a check result.
143
+
144
+ Per-item verbose diagnostics do not belong in Result. The Task SDK supports
145
+ separate bounded Run diagnostics through `context.log`; see
146
+ [Run logs](../../docs/task-run-logs.md). These checks currently emit no diagnostic
147
+ logs and do not repurpose progress or checkpoint as verbose output.
148
+ Console stderr is not a task-history logging channel. Log availability never
149
+ replaces the declared result, failure or physical release evidence.
150
+
151
+ Changing the business result contract changes the Bundle CID. Rebuild and
152
+ use the new reference from `references.json`; existing CID-addressed content
153
+ retain their original contracts.
154
+
155
+ After building, import and pin the emitted CAR using an external tool of your
156
+ choice and submit its root CID. For example, with a caller-managed local Kubo:
157
+
158
+ ```powershell
159
+ ipfs dag import --pin-roots=true packages\check-environment\dist\bundles\tools.car
160
+ ```
161
+
162
+ The Engine must be started, authorized and able to fetch the pinned CID through
163
+ its configured IPFS client. Normal task observation is unchanged:
164
+
165
+ ```js
166
+ import {readFile} from "node:fs/promises";
167
+ import {join, resolve} from "node:path";
168
+
169
+ const directory = resolve("packages", "check-environment", "dist", "bundles");
170
+ const refs = JSON.parse(await readFile(join(directory, "references.json"), "utf8"));
171
+ await engine.submit({
172
+ requestId,
173
+ bundle: refs.tools.cid,
174
+ tools: [{command: process.execPath, args: ["--version"], minimumVersion: "22.13.0"}],
175
+ timeoutMs: 5000
176
+ }, caller);
177
+ ```
178
+
179
+ Production delivery imports/pins the CAR on the caller-managed node and
180
+ publishes root CID references, optionally with gateway hints. Do not use
181
+ `latest` as executable identity. npm delivers the author/library package;
182
+ the generic Engine does not install it. This change adds local delivery
183
+ verification only, not publication workflows or release authorization.
184
+
185
+ ## Regression verification
186
+
187
+ ```powershell
188
+ pnpm --filter @potential-guide/check-environment... build
189
+ pnpm --filter @potential-guide/check-environment test
190
+ ```
191
+
192
+ The Engine regression imports the emitted CARs, verifies equality with their
193
+ directory trees and root CIDs, runs passing and unmet-requirement inputs for all
194
+ three bundles, and waits for physical release. Tool timeout/output overflow,
195
+ invalid directory paths and HTTP timeout must fail the Run without a fabricated
196
+ check result. Cancellation of an active HTTP request must stop and release the
197
+ Run without a result. Library tests additionally verify process cancellation,
198
+ write-probe cleanup, DNS, HTTP redirects and numeric version handling.
199
+
200
+ ## Objective integration
201
+
202
+ The same write/readback probe is reused by devbox-health and pguide setup
203
+ (including self-check). Devbox-health also reuses version parsing/comparison.
204
+ Its ownership/canonical-path rules, stage deadlines, command evidence, problem
205
+ IDs and actual compiler/runtime smoke tests remain in the Objective.
206
+
207
+ These existing Objective Workers call shared library code, bundled into their
208
+ own artifacts; they do **not** submit child Tasks to an Engine. The independent
209
+ bundles use the same implementations and are separately exercised through the
210
+ real Engine in tests. Network checks currently have an independent consumer
211
+ surface; devbox-health keeps its original network execution/evidence path.
212
+
213
+ Workers are not security sandboxes. Admit tool commands, filesystem paths and
214
+ network targets only from authorized callers in a trusted execution environment.
@@ -0,0 +1,19 @@
1
+ {
2
+ "kind": "directories",
3
+ "languages": [
4
+ "en"
5
+ ],
6
+ "problemCodes": [
7
+ "not-directory",
8
+ "directory-unusable"
9
+ ],
10
+ "references": {
11
+ "en": {
12
+ "check": "resources/references/en/check.md",
13
+ "problems": {
14
+ "not-directory": "resources/references/en/problems/not-directory.md",
15
+ "directory-unusable": "resources/references/en/problems/directory-unusable.md"
16
+ }
17
+ }
18
+ }
19
+ }
@@ -0,0 +1,18 @@
1
+ ---
2
+ title: Directory access
3
+ description: Check absolute directory paths and explicit exists, read or write requirements.
4
+ ---
5
+
6
+ # Directory access
7
+
8
+ Input `directories` lists absolute `path` and nonempty `requirements` from
9
+ `exists`, `read`, `write`. Symlinks are not accepted as directories.
10
+ Read uses host access checks. Write creates an exclusive probe file, reads it
11
+ back and deletes it in `finally`; it does not modify existing files.
12
+ The Engine host must have the requested permissions.
13
+
14
+ Results contain only failures: [not-directory](problems/not-directory.md) or
15
+ [directory-unusable](problems/directory-unusable.md). `target` is the absolute
16
+ path; `actual` is the filesystem error code or null. Invalid input, unexpected
17
+ filesystem errors, readback mismatch and cleanup errors remain execution
18
+ faults. Cancellation propagates after probe cleanup. No repair is performed.
@@ -0,0 +1,10 @@
1
+ ---
2
+ title: Directory unavailable
3
+ description: The directory is missing or requested access failed with a recognized filesystem error.
4
+ ---
5
+
6
+ `target` is the absolute directory path; `actual` is ENOENT, ENOTDIR, EACCES,
7
+ EPERM, EROFS, ENOSPC or EDQUOT. Check path, permissions, filesystem capacity and
8
+ mount state. Resolve the cause separately; no repair is attempted.
9
+ Unexpected errors and cleanup failures are faults, not this problem.
10
+ See [directory checks](../check.md).
@@ -0,0 +1,7 @@
1
+ ---
2
+ title: Not a directory
3
+ description: The path exists but is not an ordinary directory or is a symbolic link.
4
+ ---
5
+
6
+ `target` is the requested absolute path; `actual` is null. Select an ordinary
7
+ directory rather than a file or symlink. See [directory checks](../check.md).
@@ -0,0 +1,51 @@
1
+ {
2
+ "additionalProperties": false,
3
+ "type": "object",
4
+ "required": [
5
+ "directories"
6
+ ],
7
+ "properties": {
8
+ "directories": {
9
+ "minItems": 1,
10
+ "maxItems": 100,
11
+ "type": "array",
12
+ "items": {
13
+ "additionalProperties": false,
14
+ "type": "object",
15
+ "required": [
16
+ "path",
17
+ "requirements"
18
+ ],
19
+ "properties": {
20
+ "path": {
21
+ "minLength": 1,
22
+ "maxLength": 4096,
23
+ "type": "string"
24
+ },
25
+ "requirements": {
26
+ "minItems": 1,
27
+ "uniqueItems": true,
28
+ "type": "array",
29
+ "items": {
30
+ "anyOf": [
31
+ {
32
+ "const": "exists",
33
+ "type": "string"
34
+ },
35
+ {
36
+ "const": "read",
37
+ "type": "string"
38
+ },
39
+ {
40
+ "const": "write",
41
+ "type": "string"
42
+ }
43
+ ]
44
+ }
45
+ }
46
+ }
47
+ }
48
+ }
49
+ },
50
+ "$id": "https://task.invalid/schema/input"
51
+ }
@@ -0,0 +1,74 @@
1
+ {
2
+ "anyOf": [
3
+ {
4
+ "additionalProperties": false,
5
+ "type": "object",
6
+ "required": [
7
+ "pass"
8
+ ],
9
+ "properties": {
10
+ "pass": {
11
+ "const": true,
12
+ "type": "boolean"
13
+ }
14
+ }
15
+ },
16
+ {
17
+ "additionalProperties": false,
18
+ "type": "object",
19
+ "required": [
20
+ "pass",
21
+ "problems"
22
+ ],
23
+ "properties": {
24
+ "pass": {
25
+ "const": false,
26
+ "type": "boolean"
27
+ },
28
+ "problems": {
29
+ "minItems": 1,
30
+ "type": "array",
31
+ "items": {
32
+ "additionalProperties": false,
33
+ "type": "object",
34
+ "required": [
35
+ "target",
36
+ "code",
37
+ "actual"
38
+ ],
39
+ "properties": {
40
+ "target": {
41
+ "minLength": 1,
42
+ "maxLength": 4096,
43
+ "type": "string"
44
+ },
45
+ "code": {
46
+ "anyOf": [
47
+ {
48
+ "const": "not-directory",
49
+ "type": "string"
50
+ },
51
+ {
52
+ "const": "directory-unusable",
53
+ "type": "string"
54
+ }
55
+ ]
56
+ },
57
+ "actual": {
58
+ "anyOf": [
59
+ {
60
+ "type": "string"
61
+ },
62
+ {
63
+ "type": "null"
64
+ }
65
+ ]
66
+ }
67
+ }
68
+ }
69
+ }
70
+ }
71
+ }
72
+ ],
73
+ "$id": "https://task.invalid/schema/result"
74
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "not": {},
3
+ "$id": "https://task.invalid/schema/message"
4
+ }
@@ -0,0 +1,22 @@
1
+ $schema: https://shazhou-ww.github.io/potential-guide/schemas/artifacts/task/v1.json
2
+ name: Environment directories check
3
+ runtime:
4
+ type: node
5
+ minVersion: 22.13.0
6
+ entry: worker.cjs
7
+ inputSchema:
8
+ $ref: schemas/0.json
9
+ resultSchema:
10
+ $ref: schemas/1.json
11
+ messageSchema:
12
+ $ref: schemas/2.json
13
+ include:
14
+ - resources/check.json
15
+ - resources/references/en/check.md
16
+ - resources/references/en/problems/directory-unusable.md
17
+ - resources/references/en/problems/not-directory.md
18
+ - schemas/0.json
19
+ - schemas/1.json
20
+ - schemas/2.json
21
+ - task.yaml
22
+ - worker.cjs
@@ -0,0 +1,4 @@
1
+ const __artifactURL = require("node:url").pathToFileURL(__filename).href;
2
+ "use strict";function G(o){if(new Set(o).size!==o.length||o.some(n=>!/^[a-z][a-z0-9-]*$/.test(n)||n.length>4096))throw new Error("Check problem codes must be unique stable names of at most 4096 characters.")}function de(o,n){G(o);let i=new Set(o),r=n.map(a=>{if(!i.has(a.code))throw new Error(`Undeclared Check problem code: ${a.code}`);if(typeof a.target!="string"||!a.target.length||[...a.target].length>4096||a.actual!==null&&typeof a.actual!="string"||Object.keys(a).some(m=>!["target","code","actual"].includes(m)))throw new Error("Invalid Check problem.");return{target:a.target,code:a.code,actual:a.actual}});return r.length?{pass:!1,problems:r}:{pass:!0}}function Q(o,n){return async(i,r)=>{r.signal.throwIfAborted();let a=await n(i,r);return r.signal.throwIfAborted(),de(o,a)}}var F=require("node:worker_threads");var u=class extends Error{code;details;constructor(n,i,r){super(i),this.code=n,this.details=r,this.name="TaskError"}};function b(o,n){throw new u(o,n)}function N(o,n=65536){let i=new Set,r=0;function a(s,k){if((++r>n||k>64)&&b("invalid_payload","JSON structure exceeds limit."),s===null||typeof s=="string"||typeof s=="boolean"||typeof s=="number"&&Number.isFinite(s))return s;(typeof s!="object"||s===null)&&b("invalid_payload","Only finite JSON values are accepted."),i.has(s)&&b("invalid_payload","Cyclic JSON is not accepted.");let E=Array.isArray(s);Object.getPrototypeOf(s)!==(E?Array.prototype:Object.prototype)&&b("invalid_payload","Custom prototypes are not accepted."),i.add(s);let f=Object.getOwnPropertyDescriptors(s),w=Reflect.ownKeys(f),T={},C=[];E&&w.length!==s.length+1&&b("invalid_payload","Sparse or decorated arrays are not accepted.");for(let I of w){typeof I!="string"&&b("invalid_payload","Symbol keys are not accepted.");let x=f[I];(!x||!("value"in x))&&b("invalid_payload","Accessors are not accepted."),!(E&&I==="length")&&(x.enumerable||b("invalid_payload","Hidden properties are not accepted."),E?(I!==String(C.length)&&b("invalid_payload","Invalid array keys."),C.push(a(x.value,k+1))):Object.defineProperty(T,I,{value:a(x.value,k+1),enumerable:!0,writable:!0,configurable:!0}))}return i.delete(s),E?C:T}let m=a(o,0);return new TextEncoder().encode(JSON.stringify(m)).byteLength>n&&b("payload_too_large","JSON payload exceeds byte limit."),m}function A(o){return Array.isArray(o)?`[${o.map(A).join(",")}]`:o&&typeof o=="object"?`{${Object.keys(o).sort().map(n=>`${JSON.stringify(n)}:${A(o[n]??null)}`).join(",")}}`:JSON.stringify(o)}var R=class extends u{result;constructor(n,i,r){super(n,i),this.result=r}};var B="https://shazhou-ww.github.io/potential-guide/schemas/artifacts/task/v1.json";var K={declaration:JSON.parse('{"$schema":"https://shazhou-ww.github.io/potential-guide/schemas/artifacts/task/v1.json","name":"Environment directories check","runtime":{"type":"node","minVersion":"22.13.0"},"entry":"worker.cjs","inputSchema":{"additionalProperties":false,"type":"object","required":["directories"],"properties":{"directories":{"minItems":1,"maxItems":100,"type":"array","items":{"additionalProperties":false,"type":"object","required":["path","requirements"],"properties":{"path":{"minLength":1,"maxLength":4096,"type":"string"},"requirements":{"minItems":1,"uniqueItems":true,"type":"array","items":{"anyOf":[{"const":"exists","type":"string"},{"const":"read","type":"string"},{"const":"write","type":"string"}]}}}}}}},"resultSchema":{"anyOf":[{"additionalProperties":false,"type":"object","required":["pass"],"properties":{"pass":{"const":true,"type":"boolean"}}},{"additionalProperties":false,"type":"object","required":["pass","problems"],"properties":{"pass":{"const":false,"type":"boolean"},"problems":{"minItems":1,"type":"array","items":{"additionalProperties":false,"type":"object","required":["target","code","actual"],"properties":{"target":{"minLength":1,"maxLength":4096,"type":"string"},"code":{"anyOf":[{"const":"not-directory","type":"string"},{"const":"directory-unusable","type":"string"}]},"actual":{"anyOf":[{"type":"string"},{"type":"null"}]}}}}}}]},"messageSchema":{"not":{}},"include":["resources/check.json","resources/references/en/check.md","resources/references/en/problems/directory-unusable.md","resources/references/en/problems/not-directory.md","schemas/0.json","schemas/1.json","schemas/2.json","task.yaml","worker.cjs"]}')};var W=class{capacity;queue=[];waiter;closed=!1;constructor(n){this.capacity=n}push(n){if(this.closed||this.queue.length>=this.capacity)throw new Error("Task inbox unavailable.");this.waiter?(this.waiter({done:!1,value:n}),this.waiter=void 0):this.queue.push(n)}close(){this.closed=!0,this.queue=[],this.waiter?.({done:!0,value:void 0}),this.waiter=void 0}[Symbol.asyncIterator](){return{next:()=>{let n=this.queue.shift();return n?Promise.resolve({done:!1,value:n}):this.closed?Promise.resolve({done:!0,value:void 0}):this.waiter?Promise.reject(new Error("Task inbox has one consumer.")):new Promise(i=>{this.waiter=i})}}}};function Y(o){if(!K)throw new Error("Task SDK requires a build-locked Bundle binding.");let{declaration:n}=K,i=F.parentPort;if(!i||n.$schema!==B)throw new Error("Unsupported Task Worker.");let r=F.workerData;if(typeof r.generation!="string"||!r.generation||!Number.isSafeInteger(r.maxInFlight)||r.maxInFlight<=0||!Number.isSafeInteger(r.maxPayloadBytes)||r.maxPayloadBytes<=0||typeof r.bundle?.cid!="string"||!r.bundle.cid||r.bundle.cid.length>256)throw new Error("Invalid trusted Task Worker configuration.");if(!r.bundle||A(N(r.bundle.declaration,1048576))!==A(N(n,1048576)))throw new Error("Task Worker declaration differs from verified Bundle.");let a=new AbortController,m=new W(r.maxInFlight),s=new Map,k=new Map,E=0,f,w=!1,T=0,C=!1,I=Promise.resolve(),x,oe=0,q=new Map;function ie(t,p={}){let d=++oe,e=p.signal?AbortSignal.any([a.signal,p.signal]):a.signal,c=()=>{},g=()=>{c(),q.get(d)?.reject(new DOMException("Bundle read canceled.","AbortError")),q.delete(d),i.postMessage({generation:r.generation,runId:f,kind:"bundle-cancel",readId:d})};return new ReadableStream({start(h){let l=()=>{g(),h.error(e.reason)};if(c=()=>e.removeEventListener("abort",l),e.aborted){h.error(e.reason);return}e.addEventListener("abort",l,{once:!0})},async pull(h){try{if(w)throw new u("run_finished","Bundle stream is closed.");e.throwIfAborted();let l=await new Promise((O,ce)=>{q.set(d,{resolve:O,reject:ce}),i.postMessage({generation:r.generation,runId:f,kind:"bundle-pull",readId:d,path:t})});if(l.done)c(),h.close();else if(l.chunk instanceof Uint8Array)h.enqueue(l.chunk);else throw new u("invalid_bundle","Invalid Bundle stream chunk.")}catch(l){g(),h.error(l)}},cancel:g},{highWaterMark:0})}let D=0,S=new Map,P=0,z=0,$=!1,_=!1,X=!1,y;function ae(){if(y||_||w||a.signal.aborted||P<=z)return;let t=++D,p=P,d=setTimeout(()=>{_=!0},1e3);y={id:t,count:p,timer:d};try{i.postMessage({generation:r.generation,runId:f,kind:"log-drop",logId:t,count:p})}catch{clearTimeout(d),y=void 0,_=!0}}function Z(){$||y||_||w||a.signal.aborted||($=!0,queueMicrotask(()=>{$=!1,ae()}))}function v(){if(!(X||P<=z)){X=!0;try{i.postMessage({generation:r.generation,runId:f,kind:"log-drop",logId:++D,count:P,final:!0})}catch{}}}function U(){y&&clearTimeout(y.timer),y=void 0,_=!0;for(let t of S.values())clearTimeout(t.timer),t.resolve({state:"unavailable",reason:"channel"});S.clear()}async function se(t){if(w||a.signal.aborted)return{state:"unavailable",reason:"channel"};if(S.size>=Math.min(r.maxInFlight,16))return P=Math.min(Number.MAX_SAFE_INTEGER,P+1),Z(),{state:"dropped",reason:"capacity"};let p=D+1;return new Promise((d,e)=>{let c=setTimeout(()=>{S.delete(p),d({state:"unavailable",reason:"timeout"})},1e3);S.set(p,{resolve:d,reject:e,timer:c});try{i.postMessage({generation:r.generation,runId:f,kind:"log",logId:p,value:t}),D=p}catch(g){clearTimeout(c),S.delete(p),g instanceof Error&&g.name==="DataCloneError"?e(new u("invalid_payload","Task payload cannot be cloned for IPC.")):d({state:"unavailable",reason:"channel"})}})}function M(t,p){if(k.size>=r.maxInFlight)return Promise.reject(new Error("Task channel capacity exceeded."));let d=E+1,e=new Promise((c,g)=>{k.set(d,{resolve:c,reject:g})});try{i.postMessage({generation:r.generation,runId:f,id:d,kind:t,value:p}),E=d}catch(c){k.get(d)?.reject(c instanceof Error&&c.name==="DataCloneError"?new u("invalid_payload","Task payload cannot be cloned for IPC."):c instanceof Error?c:new Error("Task send failed.")),k.delete(d)}return e}function J(t,p){if(w)return Promise.reject(new u("run_finished","Task is finishing."));if(T>=r.maxInFlight)return Promise.reject(new Error("Task channel capacity exceeded."));T++;let d=I.then(()=>M(t,p)).catch(e=>{throw x??=e,e}).finally(()=>{T--});return I=d,d.catch(()=>{}),d}i.postMessage({generation:r.generation,kind:"ready",value:{$schema:n.$schema,cid:r.bundle.cid,inputSchema:n.inputSchema,resultSchema:n.resultSchema,messageSchema:n.messageSchema}}),i.on("message",t=>{if(!t||t.generation!==r.generation)return;if(t.kind==="bundle-data"){let e=q.get(t.readId);if(!e)return;q.delete(t.readId),t.error?e.reject(new u(t.error.code,t.error.message)):e.resolve(t);return}if(t.kind==="log-drop-ack"){if(t.runId!==f||!y||t.logId!==y.id)return;let e=y;clearTimeout(e.timer),y=void 0,t.error||t.value?.state==="unavailable"?_=!0:(z=e.count,_=!1,Z());return}if(t.kind==="log-ack"){if(t.runId!==f)return;let e=S.get(t.logId);if(!e)return;S.delete(t.logId),clearTimeout(e.timer),t.error?e.reject(new u(t.error.code,t.error.message)):e.resolve(t.value);return}if(t.kind==="ack"){let e=k.get(t.id);if(!e)return;k.delete(t.id),t.error?e.reject(new u(t.error.code,t.error.message)):e.resolve();return}if(t.kind==="ping"){i.postMessage({generation:r.generation,kind:"pong"});return}if(t.runId!==f&&t.kind!=="run")return;if(t.kind==="cancel"){i.postMessage({generation:r.generation,runId:f,kind:"stopping"}),a.abort(),U(),m.close();for(let e of s.values())e.abort();return}if(t.kind==="withdraw"){let e=s.get(t.taskId)??new AbortController;s.set(t.taskId,e),e.abort();return}if(t.kind==="message"){let e=t.value,c=s.get(e.taskId)??new AbortController;s.set(e.taskId,c);let g=!1,h=(l,O)=>g||c.signal.aborted?Promise.reject(new Error("Message unavailable.")):(g=!0,J("message-confirm",{taskId:e.taskId,messageId:e.messageId,state:l,...O===void 0?{}:{reason:O}}));m.push(Object.freeze({messageId:e.messageId,taskId:e.taskId,caller:Object.freeze(e.caller),signal:c.signal,payload:e.payload,accept:()=>h("accepted"),reject:l=>h("rejected",l)}));return}if(t.kind!=="run"||f!==void 0)return;f=t.runId;let p=Object.freeze({runId:t.runId,...t.identity?{generation:t.identity.generation,startedAt:t.identity.startedAt}:{},scope:t.scope,signal:a.signal,messages:m,bundle:Object.freeze({read:ie}),progress:e=>J("progress",e),checkpoint:e=>J("checkpoint",e),log:se}),d=async e=>{let c=e;C||=c instanceof u&&["reclamation_failed","reclamation_unknown"].includes(c.code);let g=c instanceof R?c.result:void 0,h=(l,O)=>({code:l instanceof u?l.code.slice(0,128):"run_failed",message:l instanceof Error?l.message.slice(0,512):"Task callback failed.",...O===void 0?{}:{result:O}});try{await M("failed",h(c,g))}catch(l){await M("failed",h(l))}};(async()=>{try{let e=await o(t.input,p);if(w=!0,v(),await I,x!==void 0)throw x;await M("complete",e)}catch(e){w=!0,v(),await I.catch(()=>{}),C||=e instanceof u&&["reclamation_failed","reclamation_unknown"].includes(e.code);let c=x??e;if(a.signal.aborted&&c instanceof Error&&(c.name==="AbortError"||c instanceof u&&["ABORT_ERR","run_canceled"].includes(c.code)))return;await d(c)}})().then(()=>{m.close(),a.abort(),U();for(let e of s.values())e.abort();i.postMessage({generation:r.generation,runId:f,kind:C?"uncertain":"released"}),i.close()},()=>{m.close(),a.abort(),U();for(let e of s.values())e.abort();i.postMessage({generation:r.generation,runId:f,kind:"uncertain"}),i.close()})})}var V=Object.freeze(["not-directory","directory-unusable"]);var re=require("node:crypto"),H=require("node:fs"),j=require("node:fs/promises"),L=require("node:path");function ee(o){return o.filter(n=>!n.ok).map(({target:n,code:i,actual:r})=>{if(i==="pass")throw new Error("A failing check requires a problem code.");return{target:n,code:i,actual:r}})}function te(o){return o instanceof Error&&"code"in o&&typeof o.code=="string"?o.code:void 0}async function le(o,n){n?.throwIfAborted();let i=(0,L.join)(o,`.pguide-check-${(0,re.randomUUID)()}`),r=!1;try{if(await(0,j.writeFile)(i,`directory-check
3
+ `,{flag:"wx",mode:384}),r=!0,n?.throwIfAborted(),await(0,j.readFile)(i,"utf8")!==`directory-check
4
+ `)throw new Error("Directory write probe readback mismatch.")}finally{r&&await(0,j.rm)(i)}}async function ne(o,n={}){if(!o.directories.length||o.directories.length>100)throw new Error("Specify between 1 and 100 directories.");for(let r of o.directories)if(!(0,L.isAbsolute)(r.path)||r.path.includes("\0")||!r.requirements.length||r.requirements.some(a=>!["exists","read","write"].includes(a)))throw new Error("Directories require absolute paths and explicit access requirements.");let i=[];for(let r of o.directories){n.signal?.throwIfAborted();let a="pass",m=null;try{let s=await(0,j.lstat)(r.path);!s.isDirectory()||s.isSymbolicLink()?a="not-directory":(r.requirements.includes("read")&&await(0,j.access)(r.path,H.constants.R_OK|H.constants.X_OK),r.requirements.includes("write")&&await le(r.path,n.signal))}catch(s){if(n.signal?.throwIfAborted(),m=te(s)??null,m===null||!["ENOENT","ENOTDIR","EACCES","EPERM","EROFS","ENOSPC","EDQUOT"].includes(m))throw s;a="directory-unusable"}i.push({target:r.path,ok:a==="pass",code:a,actual:m})}return ee(i)}var ue=Q(V,ne);Y(ue);
Binary file
@@ -0,0 +1,21 @@
1
+ {
2
+ "kind": "network",
3
+ "languages": [
4
+ "en"
5
+ ],
6
+ "problemCodes": [
7
+ "dns-unavailable",
8
+ "http-status",
9
+ "http-unavailable"
10
+ ],
11
+ "references": {
12
+ "en": {
13
+ "check": "resources/references/en/check.md",
14
+ "problems": {
15
+ "dns-unavailable": "resources/references/en/problems/dns-unavailable.md",
16
+ "http-status": "resources/references/en/problems/http-status.md",
17
+ "http-unavailable": "resources/references/en/problems/http-unavailable.md"
18
+ }
19
+ }
20
+ }
21
+ }
@@ -0,0 +1,21 @@
1
+ ---
2
+ title: Network reachability
3
+ description: Check explicit DNS names and HTTP status expectations with bounded resource lifetimes.
4
+ ---
5
+
6
+ # Network reachability
7
+
8
+ Input contains `dns` names and `http` entries with `url`, `expectedStatus`,
9
+ `followRedirects`; at least one check is required. Optional `checkTimeoutMs`
10
+ defaults to 5000 (100–300000). The host requires DNS/network access and Node
11
+ subprocess support. HTTP(S) URLs cannot contain credentials.
12
+ DNS runs in a bounded subprocess; HTTP response bodies are cancelled.
13
+
14
+ Problems: [DNS unavailable](problems/dns-unavailable.md),
15
+ [unexpected HTTP status](problems/http-status.md),
16
+ [HTTP unavailable](problems/http-unavailable.md).
17
+ Results contain only failures; `target` is the DNS name or URL, `actual` is
18
+ the DNS error/address, status string or fetch diagnostic.
19
+ Timeouts, invalid inputs, unexpected subprocess faults and cleanup failures
20
+ remain execution faults. Cancellation propagates and physical resources are
21
+ released. This is a point-in-time probe, not a network repair or guarantee.
@@ -0,0 +1,8 @@
1
+ ---
2
+ title: DNS unavailable
3
+ description: DNS lookup failed with a recognized availability error.
4
+ ---
5
+
6
+ `target` is the DNS name; `actual` is ENOTFOUND, EAI_AGAIN, EAI_FAIL or ETIMEOUT.
7
+ Check spelling, host DNS configuration and connectivity. Subprocess deadlines
8
+ and unexpected resolver errors remain faults. See [network checks](../check.md).
@@ -0,0 +1,9 @@
1
+ ---
2
+ title: Unexpected HTTP status
3
+ description: The HTTP response did not satisfy status and redirect expectations.
4
+ ---
5
+
6
+ `target` is the requested URL; `actual` is the decimal response status.
7
+ Check expectedStatus and followRedirects, endpoint health and authentication.
8
+ A redirect fails when following redirects is disabled, even when listed as an
9
+ expected status. See [network checks](../check.md).
@@ -0,0 +1,9 @@
1
+ ---
2
+ title: HTTP unavailable
3
+ description: Fetch failed with a network TypeError rather than a timeout or cancellation.
4
+ ---
5
+
6
+ `target` is the URL; `actual` is the fetch diagnostic string. Check host
7
+ connectivity, TLS trust and endpoint availability without treating the diagnostic
8
+ as a stable machine code. Timeout and cancellation remain faults/cancellation.
9
+ See [network checks](../check.md).
@@ -0,0 +1,76 @@
1
+ {
2
+ "additionalProperties": false,
3
+ "anyOf": [
4
+ {
5
+ "properties": {
6
+ "dns": {
7
+ "type": "array",
8
+ "minItems": 1
9
+ }
10
+ }
11
+ },
12
+ {
13
+ "properties": {
14
+ "http": {
15
+ "type": "array",
16
+ "minItems": 1
17
+ }
18
+ }
19
+ }
20
+ ],
21
+ "type": "object",
22
+ "required": [
23
+ "dns",
24
+ "http"
25
+ ],
26
+ "properties": {
27
+ "checkTimeoutMs": {
28
+ "minimum": 100,
29
+ "maximum": 300000,
30
+ "type": "integer"
31
+ },
32
+ "dns": {
33
+ "maxItems": 100,
34
+ "type": "array",
35
+ "items": {
36
+ "maxLength": 253,
37
+ "pattern": "^[A-Za-z0-9][A-Za-z0-9.-]*$",
38
+ "type": "string"
39
+ }
40
+ },
41
+ "http": {
42
+ "maxItems": 100,
43
+ "type": "array",
44
+ "items": {
45
+ "additionalProperties": false,
46
+ "type": "object",
47
+ "required": [
48
+ "url",
49
+ "expectedStatus",
50
+ "followRedirects"
51
+ ],
52
+ "properties": {
53
+ "url": {
54
+ "minLength": 1,
55
+ "maxLength": 4096,
56
+ "type": "string"
57
+ },
58
+ "expectedStatus": {
59
+ "minItems": 1,
60
+ "uniqueItems": true,
61
+ "type": "array",
62
+ "items": {
63
+ "minimum": 100,
64
+ "maximum": 599,
65
+ "type": "integer"
66
+ }
67
+ },
68
+ "followRedirects": {
69
+ "type": "boolean"
70
+ }
71
+ }
72
+ }
73
+ }
74
+ },
75
+ "$id": "https://task.invalid/schema/input"
76
+ }