@moondeck/station 0.0.1 → 1.0.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 (326) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +149 -2
  3. package/bin/uai-host.mjs +130 -0
  4. package/db/migrations/0000_host_tasks.sql +12 -0
  5. package/db/migrations/0001_host_ui.sql +11 -0
  6. package/db/migrations/0002_host_github_tokens.sql +8 -0
  7. package/db/migrations/0003_host_ssh_keys.sql +8 -0
  8. package/db/migrations/0004_host_owner_name.sql +1 -0
  9. package/db/migrations/0005_host_env_vars.sql +5 -0
  10. package/db/migrations/0006_host_project_env.sql +9 -0
  11. package/db/migrations/0007_host_github_token_kind.sql +7 -0
  12. package/db/migrations/0008_host_mcp_connections.sql +18 -0
  13. package/db/migrations/0009_host_agent_sessions.sql +15 -0
  14. package/db/migrations/0010_host_engine_accounts.sql +13 -0
  15. package/db/migrations/0011_codex_thread_id.sql +5 -0
  16. package/db/migrations/0012_agent_session_attach_key.sql +4 -0
  17. package/db/migrations/0013_github_credential_generations.sql +11 -0
  18. package/db/migrations/0014_host_inventory_event_index.sql +1 -0
  19. package/db/migrations/0015_host_settings.sql +9 -0
  20. package/db/migrations/0016_task_environment.sql +2 -0
  21. package/db/migrations/0017_engine_account_scope.sql +7 -0
  22. package/db/migrations/0018_engine_account_position.sql +3 -0
  23. package/db/migrations/0019_engine_account_store_ref.sql +3 -0
  24. package/db/migrations/0020_user_credential_store_ref.sql +6 -0
  25. package/db/migrations/0021_project_env_store_ref.sql +3 -0
  26. package/db/migrations/0022_task_project_ids.sql +5 -0
  27. package/db/migrations/0023_project_env_org.sql +4 -0
  28. package/db/migrations/0024_task_env_mode.sql +9 -0
  29. package/db/migrations/0025_project_env_authority.sql +147 -0
  30. package/db/migrations/0026_project_env_replica.sql +150 -0
  31. package/db/migrations/0027_cloud_credential_foundation.sql +51 -0
  32. package/db/migrations/0028_leased_engine_runtimes.sql +11 -0
  33. package/db/migrations/0029_task_shared_files_mode.sql +5 -0
  34. package/db/migrations/0030_credential_cleanup_restops.sql +12 -0
  35. package/db/migrations/meta/_journal.json +223 -0
  36. package/db/schema.ts +850 -0
  37. package/images/standard/Dockerfile +347 -0
  38. package/images/standard/README.md +167 -0
  39. package/images/standard/container/code-server-settings.json +63 -0
  40. package/images/standard/container/corepack-version +1 -0
  41. package/images/standard/container/uai-init +704 -0
  42. package/images/standard/container/uai-materialize-runtimes +1527 -0
  43. package/images/standard/container/uai-task-forwarder.mjs +303 -0
  44. package/images/standard/tool-versions +2 -0
  45. package/lib/__fixtures__/skill-index/SKILL.md +10 -0
  46. package/lib/__fixtures__/skill-index/skills/accessibility-checklist.md +7 -0
  47. package/lib/__fixtures__/skill-index/skills/brand-voice.md +9 -0
  48. package/lib/__fixtures__/skill-index/skills/design-tokens.md +9 -0
  49. package/lib/agent-cli.ts +1507 -0
  50. package/lib/agent-errors.ts +22 -0
  51. package/lib/agent.ts +672 -0
  52. package/lib/agents/attach-compatibility.ts +51 -0
  53. package/lib/agents/claude.ts +918 -0
  54. package/lib/agents/codex-token-usage.fixture.json +130 -0
  55. package/lib/agents/codex.ts +1392 -0
  56. package/lib/agents/cursor-period-usage.ts +253 -0
  57. package/lib/agents/cursor.ts +562 -0
  58. package/lib/agents/dispatch.ts +78 -0
  59. package/lib/agents/durable-proc.ts +334 -0
  60. package/lib/agents/factory.ts +57 -0
  61. package/lib/agents/grok.ts +381 -0
  62. package/lib/agents/kimi.ts +329 -0
  63. package/lib/agents/machine-durable.ts +604 -0
  64. package/lib/agents/mock.ts +133 -0
  65. package/lib/agents/mode.ts +64 -0
  66. package/lib/agents/opencode.ts +508 -0
  67. package/lib/agents/output-source.ts +3 -0
  68. package/lib/agents/proc.ts +221 -0
  69. package/lib/agents/rate-limit.ts +337 -0
  70. package/lib/agents/registry.ts +182 -0
  71. package/lib/agents/session-state.ts +197 -0
  72. package/lib/agents/skill-index-preamble.fixture.ts +26 -0
  73. package/lib/agents/transport.ts +741 -0
  74. package/lib/agents/types.ts +254 -0
  75. package/lib/agents/uai.ts +106 -0
  76. package/lib/agents/usage.ts +44 -0
  77. package/lib/apple-one-off.ts +277 -0
  78. package/lib/apple-runtime-recycle.ts +237 -0
  79. package/lib/apple-uninstall-teardown.ts +224 -0
  80. package/lib/assignment-evidence.ts +94 -0
  81. package/lib/attachments.ts +48 -0
  82. package/lib/browser-testing.ts +1537 -0
  83. package/lib/capture-flow.ts +332 -0
  84. package/lib/cloud-credential-cleanup-adapter.ts +123 -0
  85. package/lib/cloud-credential-host.ts +512 -0
  86. package/lib/cloud-credential-leases.ts +2587 -0
  87. package/lib/cloud-credential-runtime.ts +1357 -0
  88. package/lib/cloud-state.ts +56 -0
  89. package/lib/codex-auth.ts +456 -0
  90. package/lib/command-db.ts +310 -0
  91. package/lib/compose-network-recovery.ts +283 -0
  92. package/lib/connection-secret-redaction.ts +25 -0
  93. package/lib/container-runtime.ts +1453 -0
  94. package/lib/credential-cleanup-inventory.ts +490 -0
  95. package/lib/credential-consumers/engine-anchor.ts +1880 -0
  96. package/lib/credential-consumers/engines.ts +797 -0
  97. package/lib/credential-consumers/files.ts +934 -0
  98. package/lib/credential-consumers/github.ts +702 -0
  99. package/lib/credential-consumers/mcp.ts +389 -0
  100. package/lib/credential-consumers/task-files.ts +127 -0
  101. package/lib/credential-consumers/task-secrets.ts +10 -0
  102. package/lib/credential-store-pickup.ts +149 -0
  103. package/lib/credential-store-scope.ts +112 -0
  104. package/lib/credential-store.ts +932 -0
  105. package/lib/credentials-activation-authority.ts +709 -0
  106. package/lib/credentials-purge.ts +1006 -0
  107. package/lib/custom-connection-capture.ts +395 -0
  108. package/lib/custom-connection-launch.ts +384 -0
  109. package/lib/custom-connection-runtime.ts +141 -0
  110. package/lib/db.ts +80 -0
  111. package/lib/disk-pressure.ts +42 -0
  112. package/lib/docker-exec.ts +156 -0
  113. package/lib/engine-accounts.ts +1970 -0
  114. package/lib/engine-connect.ts +321 -0
  115. package/lib/engine-credential-env.ts +52 -0
  116. package/lib/engine-login.ts +2933 -0
  117. package/lib/engines.ts +1342 -0
  118. package/lib/enrollment-state.ts +251 -0
  119. package/lib/env-file.ts +246 -0
  120. package/lib/env-schema.ts +891 -0
  121. package/lib/env.ts +195 -0
  122. package/lib/fs-aliases.ts +49 -0
  123. package/lib/git-clone-credentials.ts +334 -0
  124. package/lib/git-diff.ts +530 -0
  125. package/lib/git-identity.ts +715 -0
  126. package/lib/github-git-auth.ts +207 -0
  127. package/lib/github-origin.ts +28 -0
  128. package/lib/github-tokens.ts +2439 -0
  129. package/lib/host-cloud-url.ts +62 -0
  130. package/lib/host-config.ts +279 -0
  131. package/lib/host-env.ts +424 -0
  132. package/lib/host-logs.ts +965 -0
  133. package/lib/internal-connections.ts +5 -0
  134. package/lib/keyed-promise-tail.ts +23 -0
  135. package/lib/launch-mode.ts +25 -0
  136. package/lib/lease.ts +239 -0
  137. package/lib/legacy-host.ts +769 -0
  138. package/lib/legacy-runtime-retirement.ts +424 -0
  139. package/lib/legacy-runtime-v1.fixture.ts +627 -0
  140. package/lib/machine-exec.ts +238 -0
  141. package/lib/machine-gateway-tunnel.ts +344 -0
  142. package/lib/machine-keys.ts +65 -0
  143. package/lib/machine-project-env-loader.ts +152 -0
  144. package/lib/machine-provider-aws.ts +923 -0
  145. package/lib/machine-provider-local.ts +338 -0
  146. package/lib/machine-provider.ts +90 -0
  147. package/lib/managed-activation-watcher.ts +72 -0
  148. package/lib/managed-install-owner-watcher.ts +55 -0
  149. package/lib/managed-operation-drain.ts +57 -0
  150. package/lib/managed-runtime.ts +4296 -0
  151. package/lib/managed-update-scheduler.ts +125 -0
  152. package/lib/mcp-capture.ts +865 -0
  153. package/lib/mcp-config-lock.ts +2 -0
  154. package/lib/mcp-connections.ts +959 -0
  155. package/lib/mcp-gateway.ts +2594 -0
  156. package/lib/obs.ts +512 -0
  157. package/lib/openrouter-budget.ts +247 -0
  158. package/lib/orchestrator.ts +11409 -0
  159. package/lib/person-capture.ts +327 -0
  160. package/lib/preview-ports.ts +85 -0
  161. package/lib/preview-sidecar.ts +331 -0
  162. package/lib/project-env-authority.ts +1319 -0
  163. package/lib/project-env-feature.ts +74 -0
  164. package/lib/project-env-legacy-tags.ts +130 -0
  165. package/lib/project-env-loader.ts +242 -0
  166. package/lib/project-env-replica.ts +2094 -0
  167. package/lib/project-env-schema-capture.ts +342 -0
  168. package/lib/project-env-staging.ts +148 -0
  169. package/lib/project-env-task-policy.ts +176 -0
  170. package/lib/project-env-task-replica.ts +1752 -0
  171. package/lib/release-feed.ts +61 -0
  172. package/lib/release-manifest.ts +710 -0
  173. package/lib/release-trust.ts +46 -0
  174. package/lib/repo-clone.ts +38 -0
  175. package/lib/routing/shadow.ts +261 -0
  176. package/lib/runner-boot.ts +10 -0
  177. package/lib/runtime-activation-tail.ts +279 -0
  178. package/lib/runtime-archive.ts +1216 -0
  179. package/lib/runtime-authority.ts +166 -0
  180. package/lib/runtime-guard.ts +36 -0
  181. package/lib/runtime-provider-state.ts +169 -0
  182. package/lib/runtime-reconnect.ts +86 -0
  183. package/lib/runtime-state.ts +1033 -0
  184. package/lib/safe-http.ts +364 -0
  185. package/lib/secrets-proxy/address.ts +95 -0
  186. package/lib/secrets-proxy/audit.ts +30 -0
  187. package/lib/secrets-proxy/ca-store.ts +200 -0
  188. package/lib/secrets-proxy/ca.ts +470 -0
  189. package/lib/secrets-proxy/container-steps.ts +138 -0
  190. package/lib/secrets-proxy/der.ts +273 -0
  191. package/lib/secrets-proxy/engine.ts +1633 -0
  192. package/lib/secrets-proxy/host.ts +93 -0
  193. package/lib/secrets-proxy/hostname.ts +57 -0
  194. package/lib/secrets-proxy/placeholders.ts +196 -0
  195. package/lib/secrets-proxy/policy.ts +150 -0
  196. package/lib/secrets-proxy/readiness.ts +148 -0
  197. package/lib/secrets-proxy/runtime.ts +582 -0
  198. package/lib/secrets-proxy/scrub.ts +559 -0
  199. package/lib/secrets-proxy/sidecar-view.ts +139 -0
  200. package/lib/secrets-proxy/task-scripts.ts +262 -0
  201. package/lib/secrets-proxy/task-trust.ts +605 -0
  202. package/lib/secrets-proxy/trust-watch.ts +80 -0
  203. package/lib/secrets.ts +71 -0
  204. package/lib/shared-files-s3.ts +976 -0
  205. package/lib/shared-files.ts +156 -0
  206. package/lib/sidecar-assign.ts +49 -0
  207. package/lib/sidecar-guard.ts +58 -0
  208. package/lib/sidecar-resume.ts +178 -0
  209. package/lib/sidecar.ts +69 -0
  210. package/lib/signin-box-credentials.ts +465 -0
  211. package/lib/signin-box.ts +114 -0
  212. package/lib/skill-index-example.fixture.ts +56 -0
  213. package/lib/skill-layout.ts +417 -0
  214. package/lib/skills.ts +489 -0
  215. package/lib/ssh.ts +401 -0
  216. package/lib/standard-image-cleanup.ts +117 -0
  217. package/lib/standard-image-pin.ts +207 -0
  218. package/lib/standard-image.ts +1697 -0
  219. package/lib/stopped-task-status-queue.ts +120 -0
  220. package/lib/task-container-cli.ts +269 -0
  221. package/lib/task-credential-cleanup.ts +752 -0
  222. package/lib/task-diff-sandbox.ts +464 -0
  223. package/lib/task-diff.ts +91 -0
  224. package/lib/task-env-forwarder.ts +228 -0
  225. package/lib/task-env-source.ts +249 -0
  226. package/lib/task-environment/anchor.ts +31 -0
  227. package/lib/task-environment/apple-container.ts +860 -0
  228. package/lib/task-environment/docker.ts +1344 -0
  229. package/lib/task-environment/index.ts +466 -0
  230. package/lib/task-environment/legacy-adoption.ts +467 -0
  231. package/lib/task-environment/machine-environment.ts +61 -0
  232. package/lib/task-environment/machine-task-up.ts +1227 -0
  233. package/lib/task-environment/machine.ts +974 -0
  234. package/lib/task-environment/registry.ts +59 -0
  235. package/lib/task-environment/self-identity.ts +20 -0
  236. package/lib/task-environment/self.ts +566 -0
  237. package/lib/task-environment/types.ts +642 -0
  238. package/lib/task-environment/workspace-files.ts +685 -0
  239. package/lib/task-environment/workspace-runtime.fixture.ts +50 -0
  240. package/lib/task-files-machine-retirement-outbox.ts +592 -0
  241. package/lib/task-files-machine-retirement.ts +116 -0
  242. package/lib/task-files-readiness.ts +127 -0
  243. package/lib/task-gateway-port.ts +22 -0
  244. package/lib/task-gateway-protocol.ts +190 -0
  245. package/lib/task-gateway-recovery.ts +47 -0
  246. package/lib/task-gateway-state.ts +512 -0
  247. package/lib/task-identity.ts +19 -0
  248. package/lib/task-inventory.ts +682 -0
  249. package/lib/task-machine-credentials-activation.ts +95 -0
  250. package/lib/task-machine-swap-safety.ts +729 -0
  251. package/lib/task-person-credentials.ts +76 -0
  252. package/lib/task-project-ops-host.ts +147 -0
  253. package/lib/task-project-ops.ts +1062 -0
  254. package/lib/task-secret-staging.ts +16 -0
  255. package/lib/task-status.ts +46 -0
  256. package/lib/task-token.ts +46 -0
  257. package/lib/token-broker-adopt.ts +344 -0
  258. package/lib/token-broker-lambda.ts +105 -0
  259. package/lib/token-broker-local.ts +392 -0
  260. package/lib/token-broker-revision.ts +310 -0
  261. package/lib/token-broker-ssm.ts +525 -0
  262. package/lib/token-broker.ts +218 -0
  263. package/lib/token-lanes/openai-oauth.ts +123 -0
  264. package/lib/token-lanes/registry.ts +45 -0
  265. package/lib/token-lanes/types.ts +163 -0
  266. package/lib/token-lanes/xai-oauth.ts +165 -0
  267. package/lib/transcript.ts +128 -0
  268. package/lib/tunnel-registry.ts +220 -0
  269. package/lib/tunnel-runtime.ts +235 -0
  270. package/lib/ulid.ts +16 -0
  271. package/lib/user-bus-env.ts +116 -0
  272. package/lib/user-credentials-store.ts +435 -0
  273. package/lib/vnode-pressure.ts +142 -0
  274. package/package.json +115 -8
  275. package/runner/runner.mjs +442 -0
  276. package/scripts/agent/_common.sh +633 -0
  277. package/scripts/agent/env-hook.sh +626 -0
  278. package/scripts/agent/moondeck-cli.sh +5 -0
  279. package/scripts/agent/task-down.sh +291 -0
  280. package/scripts/agent/task-status.sh +70 -0
  281. package/scripts/agent/task-up.sh +3761 -0
  282. package/scripts/agent/uai-env-exec.sh +26 -0
  283. package/scripts/install/darwin.ts +1048 -0
  284. package/scripts/install/linux.ts +958 -0
  285. package/scripts/install/types.ts +78 -0
  286. package/scripts/install/util.ts +254 -0
  287. package/scripts/install/win.ts +143 -0
  288. package/src/activation-respawn.ts +155 -0
  289. package/src/apple-tunnel-route.ts +104 -0
  290. package/src/capture-wire.ts +102 -0
  291. package/src/cli.ts +2583 -0
  292. package/src/cloud-credentials.ts +2144 -0
  293. package/src/cloud-origins.ts +23 -0
  294. package/src/credential-location.ts +248 -0
  295. package/src/credential-wire-validation.ts +25 -0
  296. package/src/custom-connections.ts +237 -0
  297. package/src/engine-account-paths.ts +11 -0
  298. package/src/engine-models.ts +39 -0
  299. package/src/engine-usage.ts +104 -0
  300. package/src/event-outbox.ts +185 -0
  301. package/src/index.ts +2899 -0
  302. package/src/load-env.ts +97 -0
  303. package/src/machine-tunnel-route.ts +156 -0
  304. package/src/main.ts +6349 -0
  305. package/src/never-proxied-domains.ts +31 -0
  306. package/src/outbound-frame.ts +31 -0
  307. package/src/paths.ts +81 -0
  308. package/src/project-env-latest.ts +130 -0
  309. package/src/project-env-policy.ts +396 -0
  310. package/src/project-env-wording.ts +53 -0
  311. package/src/protocol.ts +5545 -0
  312. package/src/runtime-bootstrap.ts +165 -0
  313. package/src/station-identity.ts +96 -0
  314. package/src/task-env-names.ts +28 -0
  315. package/src/task-secrets.ts +75 -0
  316. package/src/tunnel-dial.ts +102 -0
  317. package/src/ui/server.ts +852 -0
  318. package/src/ui/types.ts +173 -0
  319. package/src/workspace-path-policy.ts +41 -0
  320. package/ui/app.js +1102 -0
  321. package/ui/index.html +90 -0
  322. package/ui/style.css +685 -0
  323. package/ui/uai-favicon.svg +21 -0
  324. package/ui/uai-logo-black.svg +9 -0
  325. package/ui/uai-wheel.svg +9 -0
  326. package/bin/moondeck-station.mjs +0 -3
@@ -0,0 +1,273 @@
1
+ /**
2
+ * Minimal DER encoder (ADR-153 §3 "The CA") — exactly the X.690 subset an
3
+ * X.509 v3 certificate needs, so the runner mints its proxy certificates with
4
+ * Node built-ins and no new dependency.
5
+ *
6
+ * Every function returns one complete TLV. Nothing here ever sees key
7
+ * material: ca.ts hands the finished TBSCertificate to a signing callback and
8
+ * only embeds the signature it gets back. Inputs that DER cannot represent
9
+ * exactly throw instead of being silently adjusted, so a bad certificate can
10
+ * only fail to mint — never be minted subtly wrong.
11
+ */
12
+
13
+ const CONTEXT_SPECIFIC = 0x80;
14
+ const CONSTRUCTED = 0x20;
15
+ /** Low-tag-number form only (X.690 §8.1.2.2); X.509 never needs more. */
16
+ const MAX_TAG_NUMBER = 30;
17
+
18
+ const TAG = {
19
+ boolean: 0x01,
20
+ integer: 0x02,
21
+ bitString: 0x03,
22
+ octetString: 0x04,
23
+ null: 0x05,
24
+ objectIdentifier: 0x06,
25
+ utf8String: 0x0c,
26
+ printableString: 0x13,
27
+ ia5String: 0x16,
28
+ utcTime: 0x17,
29
+ generalizedTime: 0x18,
30
+ sequence: 0x30,
31
+ set: 0x31,
32
+ } as const;
33
+
34
+ /** Definite length: short form below 128, long form (0x80 | n, then n
35
+ * big-endian octets, no leading zero) above (X.690 §8.1.3, §10.1). */
36
+ export function encodeLength(length: number): Buffer {
37
+ if (!Number.isSafeInteger(length) || length < 0) {
38
+ throw new RangeError("DER length must be a non-negative safe integer");
39
+ }
40
+ if (length < 0x80) return Buffer.from([length]);
41
+ const octets: number[] = [];
42
+ for (let rest = length; rest > 0; rest = Math.floor(rest / 256)) {
43
+ octets.unshift(rest % 256);
44
+ }
45
+ return Buffer.from([0x80 | octets.length, ...octets]);
46
+ }
47
+
48
+ function tlv(tag: number, content: Uint8Array): Buffer {
49
+ return Buffer.concat([Buffer.from([tag]), encodeLength(content.length), content]);
50
+ }
51
+
52
+ export function sequence(...items: readonly Buffer[]): Buffer {
53
+ return tlv(TAG.sequence, Buffer.concat(items));
54
+ }
55
+
56
+ /** X.690 §11.6: a DER SET OF orders its elements by their encodings, compared
57
+ * as octet strings with the shorter one padded with trailing zero octets. */
58
+ function compareEncodings(left: Buffer, right: Buffer): number {
59
+ const length = Math.max(left.length, right.length);
60
+ for (let index = 0; index < length; index += 1) {
61
+ const difference = (left[index] ?? 0) - (right[index] ?? 0);
62
+ if (difference !== 0) return difference;
63
+ }
64
+ return 0;
65
+ }
66
+
67
+ export function set(...items: readonly Buffer[]): Buffer {
68
+ return tlv(TAG.set, Buffer.concat([...items].sort(compareEncodings)));
69
+ }
70
+
71
+ /**
72
+ * A non-negative INTEGER. A Buffer is read as an unsigned big-endian
73
+ * magnitude (a certificate serial); a number must be a safe integer. DER
74
+ * wants the minimal two's-complement form: redundant leading zero octets are
75
+ * dropped, and one is added back when the top bit would read as a sign.
76
+ */
77
+ export function integer(value: Buffer | number): Buffer {
78
+ let magnitude: Buffer;
79
+ if (typeof value === "number") {
80
+ if (!Number.isSafeInteger(value) || value < 0) {
81
+ throw new RangeError("DER INTEGER must be a non-negative safe integer");
82
+ }
83
+ const octets: number[] = [];
84
+ for (let rest = value; rest > 0; rest = Math.floor(rest / 256)) {
85
+ octets.unshift(rest % 256);
86
+ }
87
+ magnitude = Buffer.from(octets);
88
+ } else {
89
+ magnitude = value;
90
+ }
91
+ let start = 0;
92
+ while (start < magnitude.length - 1 && magnitude[start] === 0) start += 1;
93
+ let content = magnitude.length === 0 ? Buffer.from([0]) : magnitude.subarray(start);
94
+ if (((content[0] ?? 0) & 0x80) !== 0) {
95
+ content = Buffer.concat([Buffer.from([0]), content]);
96
+ }
97
+ return tlv(TAG.integer, content);
98
+ }
99
+
100
+ function base128(arc: bigint): number[] {
101
+ const octets = [Number(arc & 0x7fn)];
102
+ for (let rest = arc >> 7n; rest > 0n; rest >>= 7n) {
103
+ octets.unshift(Number(rest & 0x7fn) | 0x80);
104
+ }
105
+ return octets;
106
+ }
107
+
108
+ /** Dotted-decimal OID. Arcs are BigInt so a 2.25.<uuid> arc stays exact. */
109
+ export function objectIdentifier(oid: string): Buffer {
110
+ if (!/^[0-2](?:\.(?:0|[1-9][0-9]*))+$/.test(oid)) {
111
+ throw new Error(`invalid object identifier: ${oid}`);
112
+ }
113
+ const [first, second, ...rest] = oid.split(".").map((arc) => BigInt(arc));
114
+ if (first === undefined || second === undefined) {
115
+ throw new Error(`invalid object identifier: ${oid}`);
116
+ }
117
+ if (first < 2n && second >= 40n) {
118
+ throw new Error(`invalid object identifier: ${oid}`);
119
+ }
120
+ const octets = [first * 40n + second, ...rest].flatMap(base128);
121
+ return tlv(TAG.objectIdentifier, Buffer.from(octets));
122
+ }
123
+
124
+ export function nullValue(): Buffer {
125
+ return Buffer.from([TAG.null, 0x00]);
126
+ }
127
+
128
+ /** DER encodes TRUE as 0xFF only (X.690 §11.1). */
129
+ export function boolean(value: boolean): Buffer {
130
+ return Buffer.from([TAG.boolean, 0x01, value ? 0xff : 0x00]);
131
+ }
132
+
133
+ /** A BIT STRING whose final `unusedBits` bits are padding, which DER requires
134
+ * to be zero (X.690 §11.2.1). */
135
+ export function bitString(bytes: Buffer, unusedBits = 0): Buffer {
136
+ if (!Number.isInteger(unusedBits) || unusedBits < 0 || unusedBits > 7) {
137
+ throw new RangeError("BIT STRING unused bits must be 0..7");
138
+ }
139
+ if (bytes.length === 0 && unusedBits !== 0) {
140
+ throw new RangeError("an empty BIT STRING has no unused bits");
141
+ }
142
+ const last = bytes[bytes.length - 1] ?? 0;
143
+ if ((last & ((1 << unusedBits) - 1)) !== 0) {
144
+ throw new RangeError("DER requires the unused bits of a BIT STRING to be zero");
145
+ }
146
+ return tlv(TAG.bitString, Buffer.concat([Buffer.from([unusedBits]), bytes]));
147
+ }
148
+
149
+ /**
150
+ * A NamedBitList BIT STRING such as KeyUsage: bit 0 is the most significant
151
+ * bit of the first octet, and DER drops trailing zero bits (X.690 §11.2.2),
152
+ * so keyCertSign|cRLSign encodes as `03 02 01 06`, not `03 02 00 06`.
153
+ */
154
+ export function namedBitString(bits: readonly number[]): Buffer {
155
+ if (bits.length === 0) return bitString(Buffer.alloc(0));
156
+ for (const bit of bits) {
157
+ if (!Number.isInteger(bit) || bit < 0 || bit > 255) {
158
+ throw new RangeError("named bits must be integers 0..255");
159
+ }
160
+ }
161
+ const highest = Math.max(...bits);
162
+ const bytes = Buffer.alloc((highest >> 3) + 1);
163
+ for (const bit of bits) {
164
+ bytes[bit >> 3] = (bytes[bit >> 3] ?? 0) | (0x80 >> (bit & 7));
165
+ }
166
+ return bitString(bytes, 7 - (highest & 7));
167
+ }
168
+
169
+ export function octetString(bytes: Buffer): Buffer {
170
+ return tlv(TAG.octetString, bytes);
171
+ }
172
+
173
+ export function utf8String(value: string): Buffer {
174
+ const bytes = Buffer.from(value, "utf8");
175
+ // Buffer.from silently turns a lone surrogate into U+FFFD; refuse instead.
176
+ if (bytes.toString("utf8") !== value) {
177
+ throw new Error("UTF8String must be well-formed Unicode");
178
+ }
179
+ return tlv(TAG.utf8String, bytes);
180
+ }
181
+
182
+ export function printableString(value: string): Buffer {
183
+ if (!/^[A-Za-z0-9 '()+,\-./:=?]*$/.test(value)) {
184
+ throw new Error("value is outside the PrintableString alphabet");
185
+ }
186
+ return tlv(TAG.printableString, Buffer.from(value, "ascii"));
187
+ }
188
+
189
+ export function ia5String(value: string): Buffer {
190
+ if (!/^[\x00-\x7f]*$/.test(value)) {
191
+ throw new Error("value is outside the IA5String alphabet");
192
+ }
193
+ return tlv(TAG.ia5String, Buffer.from(value, "ascii"));
194
+ }
195
+
196
+ function wholeSeconds(date: Date): void {
197
+ if (!Number.isFinite(date.getTime())) throw new RangeError("invalid date");
198
+ // X.509 times carry no fraction (RFC 5280 §4.1.2.5); refuse rather than
199
+ // truncate, so the caller's notBefore/notAfter is exactly what is signed.
200
+ if (date.getUTCMilliseconds() !== 0) {
201
+ throw new RangeError("certificate times must be whole seconds");
202
+ }
203
+ }
204
+
205
+ function pad(value: number, width: number): string {
206
+ return String(value).padStart(width, "0");
207
+ }
208
+
209
+ function monthToSecond(date: Date): string {
210
+ return (
211
+ pad(date.getUTCMonth() + 1, 2) +
212
+ pad(date.getUTCDate(), 2) +
213
+ pad(date.getUTCHours(), 2) +
214
+ pad(date.getUTCMinutes(), 2) +
215
+ pad(date.getUTCSeconds(), 2)
216
+ );
217
+ }
218
+
219
+ /** YYMMDDHHMMSSZ — RFC 5280 reads YY ≥ 50 as 19YY, so only 1950–2049 fit. */
220
+ export function utcTime(date: Date): Buffer {
221
+ wholeSeconds(date);
222
+ const year = date.getUTCFullYear();
223
+ if (year < 1950 || year > 2049) {
224
+ throw new RangeError("UTCTime covers 1950 through 2049 only");
225
+ }
226
+ return tlv(TAG.utcTime, Buffer.from(`${pad(year % 100, 2)}${monthToSecond(date)}Z`, "ascii"));
227
+ }
228
+
229
+ /** YYYYMMDDHHMMSSZ, no fractional seconds (RFC 5280 §4.1.2.5.2). */
230
+ export function generalizedTime(date: Date): Buffer {
231
+ wholeSeconds(date);
232
+ const year = date.getUTCFullYear();
233
+ if (year < 0 || year > 9999) {
234
+ throw new RangeError("GeneralizedTime covers years 0000 through 9999");
235
+ }
236
+ return tlv(TAG.generalizedTime, Buffer.from(`${pad(year, 4)}${monthToSecond(date)}Z`, "ascii"));
237
+ }
238
+
239
+ /** RFC 5280 §4.1.2.5: UTCTime through 2049, GeneralizedTime from 2050 on. */
240
+ export function time(date: Date): Buffer {
241
+ const year = date.getUTCFullYear();
242
+ return year >= 1950 && year <= 2049 ? utcTime(date) : generalizedTime(date);
243
+ }
244
+
245
+ function checkTagNumber(tagNumber: number): number {
246
+ if (!Number.isInteger(tagNumber) || tagNumber < 0 || tagNumber > MAX_TAG_NUMBER) {
247
+ throw new RangeError(`context tag number must be 0..${MAX_TAG_NUMBER}`);
248
+ }
249
+ return tagNumber;
250
+ }
251
+
252
+ /** `[n] EXPLICIT`: a constructed context-specific TLV wrapping the whole inner
253
+ * TLV (e.g. the certificate version `A0 03 02 01 02`). */
254
+ export function explicit(tagNumber: number, inner: Buffer): Buffer {
255
+ return tlv(CONTEXT_SPECIFIC | CONSTRUCTED | checkTagNumber(tagNumber), inner);
256
+ }
257
+
258
+ /**
259
+ * `[n] IMPLICIT`: the context-specific tag REPLACES the inner type's own tag,
260
+ * keeping its length and content octets and its constructed bit — e.g. a
261
+ * SAN dNSName is `[2] IMPLICIT IA5String`, `82 len name`. Takes the encoded
262
+ * inner TLV so callers reuse the typed encoders above.
263
+ */
264
+ export function implicit(tagNumber: number, inner: Buffer): Buffer {
265
+ const identifier = inner[0];
266
+ if (identifier === undefined || inner.length < 2 || (identifier & 0x1f) === 0x1f) {
267
+ throw new Error("implicit tagging needs a complete low-tag-number TLV");
268
+ }
269
+ return Buffer.concat([
270
+ Buffer.from([CONTEXT_SPECIFIC | (identifier & CONSTRUCTED) | checkTagNumber(tagNumber)]),
271
+ inner.subarray(1),
272
+ ]);
273
+ }