@volter/twin-fly 0.1.0 → 0.1.2

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.
@@ -40,7 +40,7 @@
40
40
  // speaks): e.g. lease responses are WRAPPED `{status:"success", data:{...}}` (fly-go MachineLease),
41
41
  // not the OpenAPI's bare Lease schema. See spec-sources.json for the full citation + every
42
42
  // ⚠ doc-unverified modeling choice.
43
- import { applyTwinWrite, projectResources } from '@volter/world-core';
43
+ import { applyTwinWrite, projectResources, worldNow } from '@volter/world-core';
44
44
  import { createHash } from 'node:crypto';
45
45
  export const SERVICE = 'fly';
46
46
  /**
@@ -96,7 +96,7 @@ export const FLY_MACHINE_STATES = [
96
96
  // (created_at, instance_id, image_ref, …) so nothing collides; the bare vendor id is re-attached
97
97
  // AFTER projection, never written inside `fields`.
98
98
  export function nowIso(occurredAt) {
99
- return occurredAt ?? new Date().toISOString();
99
+ return occurredAt ?? worldNow();
100
100
  }
101
101
  export function nowEpochMs(occurredAt) {
102
102
  return Date.parse(nowIso(occurredAt));
@@ -11,7 +11,7 @@
11
11
  // engine is a provider behind a door, never the serve path's business.
12
12
  import { serveHttp } from '@volter/world-core';
13
13
  import { rows, VIRTUAL_FLY_RUNTIME } from "./fly-machines.js";
14
- import { worldNow, statefulTwinManifest } from '@volter/world-core';
14
+ import { isReadOnlyRequest, worldNow, statefulTwinManifest, withRequestScopes } from '@volter/world-core';
15
15
  import { handleFlyTwinRequest } from "./fly-twin.js";
16
16
  /**
17
17
  * The pack's whole HTTP surface as a plain fetch (runtime contract R12b): `createFlyTwinServer`
@@ -23,10 +23,13 @@ import { handleFlyTwinRequest } from "./fly-twin.js";
23
23
  * header map, which the adapter's fixed request shape does not thread.
24
24
  */
25
25
  export function createFlyTwinFetch(options = {}) {
26
- const readOnly = options.readOnly ?? false;
27
26
  const root = options.root; // undefined resolves through the kernel's projectRoot — the serve path never reads the process (R4)
28
27
  const runtime = options.runtime ?? VIRTUAL_FLY_RUNTIME;
29
- return async function flyTwinFetch(request) {
28
+ // a read-only request (x-volter-read-only) is a request to a read-only twin: a machine read answers the
29
+ // control plane's current state without folding a runtime exit into it (no inspect, no restart, no
30
+ // write), nothing is written (withRequestScopes: the pack opts in)
31
+ return withRequestScopes(async function flyTwinFetch(request) {
32
+ const readOnly = (options.readOnly ?? false) || isReadOnlyRequest(request);
30
33
  const url = new URL(request.url);
31
34
  // GET /twin — the discovery manifest (education inside the twin). Constants only: no
32
35
  // clock, no state, so every replay of this door is byte-identical (R9).
@@ -49,7 +52,7 @@ export function createFlyTwinFetch(options = {}) {
49
52
  if (status === 204 || out === null)
50
53
  return new Response(null, { status });
51
54
  return new Response(JSON.stringify(out), { status, headers: { 'content-type': 'application/json' } });
52
- };
55
+ }, { refuse: () => Response.json({ error: 'read-only twin: writes are disabled' }, { status: 405 }) });
53
56
  }
54
57
  export async function createFlyTwinServer(options = {}) {
55
58
  const readOnly = options.readOnly ?? false;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@volter/twin-fly",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Local Fly.io Machines API twin (api.machines.dev v1: apps/machines/volumes/secrets CRUD, machine lifecycle states+events, leases, wait) whose execution plane is REAL local Docker — control plane twinned, containers genuinely run. Built on @volter/world-core.",
5
5
  "author": "Volter (https://github.com/volter-ai)",
6
6
  "license": "Apache-2.0",
@@ -36,12 +36,12 @@
36
36
  "postpack": "node ../../../scripts/publish/prepare-publish.mjs postpack"
37
37
  },
38
38
  "peerDependencies": {
39
- "@volter/world-core": "2.0.0"
39
+ "@volter/world-core": "2.0.2"
40
40
  },
41
41
  "devDependencies": {
42
42
  "@types/bun": "^1.2.20",
43
43
  "@types/node": "^24.0.0",
44
- "@volter/world-core": "2.0.0",
44
+ "@volter/world-core": "2.0.2",
45
45
  "@volter/world-tooling": "0.1.0",
46
46
  "typescript": "^5.9.0"
47
47
  },
@@ -40,7 +40,7 @@
40
40
  // speaks): e.g. lease responses are WRAPPED `{status:"success", data:{...}}` (fly-go MachineLease),
41
41
  // not the OpenAPI's bare Lease schema. See spec-sources.json for the full citation + every
42
42
  // ⚠ doc-unverified modeling choice.
43
- import { applyTwinWrite, projectResources } from '@volter/world-core';
43
+ import { applyTwinWrite, projectResources, worldNow } from '@volter/world-core';
44
44
  import { createHash } from 'node:crypto';
45
45
 
46
46
  export const SERVICE = 'fly';
@@ -172,7 +172,7 @@ export type FlyMachineState = typeof FLY_MACHINE_STATES[number];
172
172
  // AFTER projection, never written inside `fields`.
173
173
 
174
174
  export function nowIso(occurredAt?: string): string {
175
- return occurredAt ?? new Date().toISOString();
175
+ return occurredAt ?? worldNow();
176
176
  }
177
177
  export function nowEpochMs(occurredAt?: string): number {
178
178
  return Date.parse(nowIso(occurredAt));
package/src/fly-server.ts CHANGED
@@ -11,7 +11,7 @@
11
11
  // engine is a provider behind a door, never the serve path's business.
12
12
  import { serveHttp } from '@volter/world-core';
13
13
  import { rows, VIRTUAL_FLY_RUNTIME, type FlyContainerRuntime } from './fly-machines.ts';
14
- import { worldNow, statefulTwinManifest} from '@volter/world-core';
14
+ import { isReadOnlyRequest, worldNow, statefulTwinManifest, withRequestScopes } from '@volter/world-core';
15
15
  import { handleFlyTwinRequest } from './fly-twin.ts';
16
16
 
17
17
  /** Options every Fly-twin HTTP surface needs, independent of who owns the socket. The
@@ -34,10 +34,13 @@ export interface FlyTwinFetchOptions {
34
34
  * header map, which the adapter's fixed request shape does not thread.
35
35
  */
36
36
  export function createFlyTwinFetch(options: FlyTwinFetchOptions = {}): (request: Request) => Promise<Response> {
37
- const readOnly = options.readOnly ?? false;
38
37
  const root = options.root; // undefined resolves through the kernel's projectRoot — the serve path never reads the process (R4)
39
38
  const runtime = options.runtime ?? VIRTUAL_FLY_RUNTIME;
40
- return async function flyTwinFetch(request: Request): Promise<Response> {
39
+ // a read-only request (x-volter-read-only) is a request to a read-only twin: a machine read answers the
40
+ // control plane's current state without folding a runtime exit into it (no inspect, no restart, no
41
+ // write), nothing is written (withRequestScopes: the pack opts in)
42
+ return withRequestScopes(async function flyTwinFetch(request: Request): Promise<Response> {
43
+ const readOnly = (options.readOnly ?? false) || isReadOnlyRequest(request);
41
44
  const url = new URL(request.url);
42
45
  // GET /twin — the discovery manifest (education inside the twin). Constants only: no
43
46
  // clock, no state, so every replay of this door is byte-identical (R9).
@@ -59,7 +62,7 @@ export function createFlyTwinFetch(options: FlyTwinFetchOptions = {}): (request:
59
62
  });
60
63
  if (status === 204 || out === null) return new Response(null, { status });
61
64
  return new Response(JSON.stringify(out), { status, headers: { 'content-type': 'application/json' } });
62
- };
65
+ }, { refuse: () => Response.json({ error: 'read-only twin: writes are disabled' }, { status: 405 }) });
63
66
  }
64
67
 
65
68
  export async function createFlyTwinServer(