@forgezero/agent 0.1.11 → 0.1.13

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 (48) hide show
  1. package/README.md +36 -9
  2. package/dist/deployment.d.ts +1 -1
  3. package/dist/fz-agent.js +545 -488
  4. package/dist/fz.js +34 -12
  5. package/dist/guest-enrolment.d.ts +2 -1
  6. package/dist/guest-enrolment.js +81 -23
  7. package/dist/index.d.ts +5 -29
  8. package/dist/metal-helper-socket.js +152 -26
  9. package/dist/metal-provision.d.ts +4 -3
  10. package/dist/metal-provision.js +152 -26
  11. package/dist/node-vault.d.ts +9 -0
  12. package/dist/node-vault.js +53 -17
  13. package/dist/provision.d.ts +1 -0
  14. package/dist/provision.js +14 -4
  15. package/dist/provisioning-pull.d.ts +22 -1
  16. package/dist/provisioning-pull.js +21 -10
  17. package/dist/signed-node-http.d.ts +1 -1
  18. package/dist/socket.d.ts +11 -4
  19. package/dist/ubuntu.d.ts +16 -0
  20. package/dist/ubuntu.js +18 -0
  21. package/dist/version.d.ts +2 -0
  22. package/package.json +11 -6
  23. package/dist/attestation-client.test.d.ts +0 -1
  24. package/dist/cache.test.d.ts +0 -1
  25. package/dist/cli/agent-install.test.d.ts +0 -1
  26. package/dist/cli/options.test.d.ts +0 -1
  27. package/dist/cli/run.test.d.ts +0 -1
  28. package/dist/compute.test.d.ts +0 -1
  29. package/dist/control.test.d.ts +0 -1
  30. package/dist/definition.test.d.ts +0 -1
  31. package/dist/deployment-pull.test.d.ts +0 -1
  32. package/dist/deployment-runner.test.d.ts +0 -1
  33. package/dist/deployment-watch.d.ts +0 -36
  34. package/dist/deployment-watch.test.d.ts +0 -1
  35. package/dist/deployment.test.d.ts +0 -1
  36. package/dist/guest-enrolment.test.d.ts +0 -1
  37. package/dist/index.test.d.ts +0 -1
  38. package/dist/metal-helper-socket.test.d.ts +0 -1
  39. package/dist/metal-isolation.test.d.ts +0 -1
  40. package/dist/metal-provision.test.d.ts +0 -1
  41. package/dist/node-vault.test.d.ts +0 -1
  42. package/dist/pipeline.test.d.ts +0 -1
  43. package/dist/provisioning-pull.test.d.ts +0 -1
  44. package/dist/snp-attestation.test.d.ts +0 -1
  45. package/dist/socket.test.d.ts +0 -1
  46. package/dist/ssh-listen.test.d.ts +0 -1
  47. package/dist/ssh-server.test.d.ts +0 -1
  48. package/dist/subscribe.test.d.ts +0 -1
@@ -1,5 +1,11 @@
1
1
  // src/signed-node-http.ts
2
- import { signRequest } from "@forgezero/runtime/identity";
2
+ import {
3
+ encodeSignatureHeader,
4
+ generateResponseRecipient,
5
+ openResponse,
6
+ RESPONSE_KEY_HEADER,
7
+ signRequest
8
+ } from "@forgezero/runtime/identity";
3
9
 
4
10
  class SignedNodeHttpError extends Error {
5
11
  status;
@@ -9,30 +15,28 @@ class SignedNodeHttpError extends Error {
9
15
  this.name = "SignedNodeHttpError";
10
16
  }
11
17
  }
12
- var signatureHeader = (envelope) => Buffer.from(JSON.stringify({
13
- timestamp: envelope.timestamp,
14
- nonce: envelope.nonce,
15
- edSignature: envelope.edSignature,
16
- mlDsaSignature: envelope.mlDsaSignature
17
- })).toString("base64url");
18
- async function postSignedNode(options, path, body) {
18
+ async function postSignedNode(options, path, body, sealedResponse = false) {
19
19
  const url = new URL(options.apiUrl);
20
20
  url.pathname = `${url.pathname.replace(/\/$/, "")}/${path.replace(/^\//, "")}`.replace(/\/+/g, "/");
21
21
  url.search = "";
22
22
  url.hash = "";
23
23
  const raw = JSON.stringify(body);
24
+ const recipient = sealedResponse ? generateResponseRecipient() : undefined;
24
25
  const envelope = signRequest(options.keys, options.nodeKey, {
25
26
  method: "POST",
26
27
  path: url.pathname,
27
28
  query: "",
28
- body: raw
29
+ body: raw,
30
+ responseKey: recipient?.publicKey
29
31
  });
32
+ const signature = encodeSignatureHeader(envelope);
30
33
  const response = await (options.fetch ?? globalThis.fetch)(url, {
31
34
  method: "POST",
32
35
  headers: {
33
36
  "content-type": "application/json",
34
37
  "x-fz-node": options.nodeKey,
35
- "x-fz-signature": signatureHeader(envelope)
38
+ "x-fz-signature": signature,
39
+ ...recipient ? { [RESPONSE_KEY_HEADER]: recipient.publicKey } : {}
36
40
  },
37
41
  body: raw,
38
42
  signal: AbortSignal.timeout(options.requestTimeoutMs ?? 15000)
@@ -43,6 +47,13 @@ async function postSignedNode(options, path, body) {
43
47
  const reason = failure ? failure.error?.message ?? failure.message : undefined;
44
48
  throw new SignedNodeHttpError(response.status, reason || `signed node request returned HTTP ${response.status}`);
45
49
  }
50
+ if (recipient) {
51
+ try {
52
+ return await openResponse(recipient.secretKey, signature, payload);
53
+ } catch {
54
+ throw new SignedNodeHttpError(502, "The node response was not sealed to this request.");
55
+ }
56
+ }
46
57
  return payload;
47
58
  }
48
59
 
@@ -11,4 +11,4 @@ export declare class SignedNodeHttpError extends Error {
11
11
  constructor(status: number, message: string);
12
12
  }
13
13
  /** One implementation of the hybrid-signed machine HTTP contract. */
14
- export declare function postSignedNode<T>(options: SignedNodeHttpOptions, path: string, body: object): Promise<T>;
14
+ export declare function postSignedNode<T>(options: SignedNodeHttpOptions, path: string, body: object, sealedResponse?: boolean): Promise<T>;
package/dist/socket.d.ts CHANGED
@@ -27,10 +27,11 @@ import type { SecretCache } from './cache';
27
27
  *
28
28
  * ## Peer credentials, not a shared secret
29
29
  *
30
- * Access control is the filesystem's: the socket is created 0600, owned by the
31
- * user the agent runs as, and only that user's processes can connect. A token
32
- * checked over the socket would have to live somewhere both sides can read,
33
- * which is the problem this exists to remove rather than a solution to it.
30
+ * Access control is the filesystem's: the socket is created 0660 under the
31
+ * dedicated `forgezero-vault` service group. An application service account is
32
+ * admitted by group membership; unrelated users and the deployment runner are
33
+ * not. A token checked over the socket would have to live somewhere both sides
34
+ * can read, which is the problem this exists to remove rather than a solution.
34
35
  */
35
36
  export declare class AgentError extends Error {
36
37
  readonly code: string;
@@ -50,11 +51,15 @@ export type Request = {
50
51
  nonce?: string;
51
52
  } | {
52
53
  op: 'get';
54
+ project: string;
55
+ environment: string;
53
56
  name: string;
54
57
  } | {
55
58
  op: 'sync';
56
59
  } | {
57
60
  op: 'held';
61
+ project: string;
62
+ environment: string;
58
63
  };
59
64
  export type Response = {
60
65
  ok: true;
@@ -125,6 +130,8 @@ export interface AgentOptions {
125
130
  * database URL fails somewhere far from here.
126
131
  */
127
132
  cache?: SecretCache;
133
+ /** Project boundary attached by the platform during one-time enrolment. */
134
+ projectKey?: string;
128
135
  /** Every request, for the hash-chained journal. */
129
136
  record?: (entry: {
130
137
  op: string;
@@ -0,0 +1,16 @@
1
+ /**
2
+ * The only guest operating system ForgeZero currently supports.
3
+ *
4
+ * A dated Canonical release directory and its published SHA-256 make image
5
+ * acquisition reproducible. `release/current` is deliberately forbidden: it
6
+ * changes underneath an already reviewed metal profile.
7
+ */
8
+ export declare const SUPPORTED_GUEST_IMAGE: Readonly<{
9
+ readonly key: "ubuntu-resolute-20260731";
10
+ readonly family: "ubuntu-26.04";
11
+ readonly version: "2026-07-31";
12
+ readonly label: "Ubuntu 26.04 LTS Resolute";
13
+ readonly url: "https://cloud-images.ubuntu.com/releases/resolute/release-20260731/ubuntu-26.04-server-cloudimg-amd64.img";
14
+ readonly sha256: "9dc7c5363c0146a08ba0c9aa834d82c2c6dfbb1c471ad9a2f0aba1189e21be05";
15
+ }>;
16
+ export declare function assertSupportedGuestImage(imageKey: string): void;
package/dist/ubuntu.js ADDED
@@ -0,0 +1,18 @@
1
+ // src/ubuntu.ts
2
+ var SUPPORTED_GUEST_IMAGE = Object.freeze({
3
+ key: "ubuntu-resolute-20260731",
4
+ family: "ubuntu-26.04",
5
+ version: "2026-07-31",
6
+ label: "Ubuntu 26.04 LTS Resolute",
7
+ url: "https://cloud-images.ubuntu.com/releases/resolute/release-20260731/ubuntu-26.04-server-cloudimg-amd64.img",
8
+ sha256: "9dc7c5363c0146a08ba0c9aa834d82c2c6dfbb1c471ad9a2f0aba1189e21be05"
9
+ });
10
+ function assertSupportedGuestImage(imageKey) {
11
+ if (imageKey !== SUPPORTED_GUEST_IMAGE.key) {
12
+ throw new Error(`unsupported guest image ${imageKey}; ForgeZero currently supports only ${SUPPORTED_GUEST_IMAGE.key}`);
13
+ }
14
+ }
15
+ export {
16
+ assertSupportedGuestImage,
17
+ SUPPORTED_GUEST_IMAGE
18
+ };
@@ -0,0 +1,2 @@
1
+ /** One package version shared by both public binaries. Pinned to package.json by tests. */
2
+ export declare const VERSION = "0.1.13";
package/package.json CHANGED
@@ -1,11 +1,12 @@
1
1
  {
2
2
  "//": "Publishing happens from an operator's machine, not CI \u2014 CLAUDE.md records that the absence of CI is deliberate. npm's `provenance` attests a tarball was built by a recognised CI provider from a named commit, so it cannot be produced here: it was set, and the first publish failed with `Automatic provenance generation not supported for provider: null`. A setting that can never be satisfied is worse than none, because it reads as a guarantee nobody is getting. Restore it the day this publishes from CI, and not before.",
3
3
  "name": "@forgezero/agent",
4
- "version": "0.1.11",
4
+ "version": "0.1.13",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "check": "tsc --noEmit",
8
- "build": "bun build src/index.ts --outfile dist/fz-agent.js --target bun --format esm --packages external && bun build src/cli/index.ts --outfile dist/fz.js --target bun --format esm --packages external && bun build src/compute.ts src/provision.ts src/subscribe.ts src/pipeline.ts src/definition.ts src/ssh-server.ts src/ssh-listen.ts src/provisioning-pull.ts src/guest-enrolment.ts src/node-vault.ts src/metal-provision.ts src/metal-helper-socket.ts src/deployment-runner.ts --root src --outdir dist --target browser --format esm --packages external && tsc --emitDeclarationOnly --declaration --noEmit false --outDir dist",
8
+ "prebuild": "rm -rf dist",
9
+ "build": "bun build src/index.ts --outfile dist/fz-agent.js --target bun --format esm --packages external && bun build src/cli/index.ts --outfile dist/fz.js --target bun --format esm --packages external && bun build src/compute.ts src/provision.ts src/subscribe.ts src/pipeline.ts src/definition.ts src/ssh-server.ts src/ssh-listen.ts src/provisioning-pull.ts src/guest-enrolment.ts src/node-vault.ts src/metal-provision.ts src/metal-helper-socket.ts src/deployment-runner.ts src/ubuntu.ts --root src --outdir dist --target browser --format esm --packages external && tsc --emitDeclarationOnly --declaration --noEmit false --outDir dist",
9
10
  "prepublishOnly": "bun run check && bun run build"
10
11
  },
11
12
  "devDependencies": {
@@ -17,16 +18,16 @@
17
18
  },
18
19
  "dependencies": {
19
20
  "@forgezero/access": "^0.1.0",
20
- "@forgezero/runtime": "^0.1.2",
21
- "@forgezero/vault": "^0.1.1",
21
+ "@forgezero/runtime": "^0.1.3",
22
+ "@forgezero/vault": "^0.1.5",
22
23
  "@noble/curves": "^2.2.0",
23
24
  "@noble/hashes": "^2.2.0",
24
25
  "@noble/post-quantum": "^0.6.1",
25
26
  "@scure/bip39": "^2.2.0"
26
27
  },
27
28
  "bin": {
28
- "fz-agent": "./dist/fz-agent.js",
29
- "fz": "./dist/fz.js"
29
+ "fz-agent": "dist/fz-agent.js",
30
+ "fz": "dist/fz.js"
30
31
  },
31
32
  "publishConfig": {
32
33
  "access": "public"
@@ -90,6 +91,10 @@
90
91
  "./metal-provision": {
91
92
  "types": "./dist/metal-provision.d.ts",
92
93
  "default": "./dist/metal-provision.js"
94
+ },
95
+ "./ubuntu": {
96
+ "types": "./dist/ubuntu.d.ts",
97
+ "default": "./dist/ubuntu.js"
93
98
  }
94
99
  }
95
100
  }
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1,36 +0,0 @@
1
- import type { DeploymentManager } from './deployment';
2
- export type StaticDeploymentOutcome = 'pending' | 'running' | 'deployed' | 'failed';
3
- export interface StaticDeploymentState {
4
- revision: string;
5
- outcome: StaticDeploymentOutcome;
6
- updatedAtTs: number;
7
- detail?: string;
8
- }
9
- export interface StaticDeploymentWatchOptions {
10
- manager: DeploymentManager;
11
- statePath: string;
12
- coordinator?: boolean;
13
- intervalMs?: number;
14
- readState?: () => StaticDeploymentState | null;
15
- writeState?: (state: StaticDeploymentState) => void;
16
- /** Active release on the first watcher boot, before a state file exists. */
17
- currentRevision?: () => string | undefined;
18
- setTimer?: (callback: () => void, ms: number) => unknown;
19
- clearTimer?: (handle: unknown) => void;
20
- now?: () => number;
21
- onEvent?: (event: string, detail?: unknown) => void;
22
- }
23
- export declare function readStaticDeploymentState(path: string): StaticDeploymentState | null;
24
- export declare function writeStaticDeploymentState(path: string, state: StaticDeploymentState): void;
25
- /**
26
- * Git is only an intake adapter. Every discovered revision is still executed
27
- * by DeploymentManager and therefore by the common keyed async queue.
28
- *
29
- * The state file is business recovery state, not queue persistence: pending
30
- * and running are written before execution, so a process crash re-submits the
31
- * exact commit on boot; deployed is written only after the awaited result.
32
- */
33
- export declare function startStaticDeploymentWatch(options: StaticDeploymentWatchOptions): {
34
- stop(): Promise<void>;
35
- readonly active: boolean;
36
- };
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};