@intentius/chant-lexicon-fly 0.18.7 → 0.18.9

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.
@@ -16,5 +16,5 @@
16
16
  /** Pinned mudflaps (Fly Machines / flaps emulator) image. */
17
17
  export declare const MUDFLAPS_IMAGE = "ghcr.io/intentius/mudflaps:0.4.0";
18
18
  /** Pinned spritzer (Fly Sprites emulator) image. */
19
- export declare const SPRITZER_IMAGE = "ghcr.io/intentius/spritzer:0.3.1";
19
+ export declare const SPRITZER_IMAGE = "ghcr.io/intentius/spritzer:0.4.0";
20
20
  //# sourceMappingURL=emulator-images.d.ts.map
@@ -5,8 +5,8 @@
5
5
  * the fly resource surface generates + drift-checks against. The Sprites API
6
6
  * (api.sprites.dev) ships no such spec at any conventional path, so there is
7
7
  * nothing to diff automatically. This module is the manual stand-in: the exact
8
- * endpoint set the fly sprite activities (./sprites.ts) depend on, maintained by
9
- * hand from https://docs.sprites.dev.
8
+ * endpoint set the fly sprite activities (lifecycle, filesystem, config, tasks)
9
+ * depend on, maintained by hand from https://docs.sprites.dev.
10
10
  *
11
11
  * It anchors two fidelity checks:
12
12
  * 1. Coverage — every endpoint here must be served by the pinned spritzer
@@ -23,15 +23,19 @@
23
23
  /** One endpoint the fly sprite activities call. */
24
24
  export interface SpritesEndpoint {
25
25
  /** HTTP method, or "WS" for the control-WebSocket exec channel. */
26
- method: "GET" | "POST" | "DELETE" | "WS";
26
+ method: "GET" | "POST" | "PUT" | "DELETE" | "WS";
27
27
  /** Path template under the Sprites base, e.g. `/v1/sprites/{id}/checkpoint`. */
28
28
  path: string;
29
- /** The fly activity that calls it (./sprites.ts export). */
29
+ /** The fly activity that calls it (an activity module export). */
30
30
  activity: string;
31
31
  }
32
32
  /**
33
- * The Sprites endpoints ./sprites.ts depends on. Keep in sync with the activity
34
- * implementations the unit test asserts every sprite activity is represented.
33
+ * The Sprites endpoints the fly sprite activities depend on, across the lifecycle
34
+ * (./sprites.ts), filesystem (./sprite-fs.ts), config reconcile
35
+ * (./sprite-config.ts), and keep-alive tasks (./sprite-tasks.ts) modules. Keep in
36
+ * sync with the activity implementations — the unit test asserts every sprite
37
+ * activity is represented, and the coverage test asserts the pinned spritzer
38
+ * serves each one.
35
39
  */
36
40
  export declare const SPRITES_CONTRACT: readonly SpritesEndpoint[];
37
41
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"sprites-contract.d.ts","sourceRoot":"","sources":["../../../src/op/activities/sprites-contract.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,mDAAmD;AACnD,MAAM,WAAW,eAAe;IAC9B,mEAAmE;IACnE,MAAM,EAAE,KAAK,GAAG,MAAM,GAAG,QAAQ,GAAG,IAAI,CAAC;IACzC,gFAAgF;IAChF,IAAI,EAAE,MAAM,CAAC;IACb,4DAA4D;IAC5D,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;;GAGG;AACH,eAAO,MAAM,gBAAgB,EAAE,SAAS,eAAe,EAO7C,CAAC;AAEX;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAItE;AAED,8DAA8D;AAC9D,wBAAgB,YAAY,IAAI,GAAG,CAAC,MAAM,CAAC,CAE1C"}
1
+ {"version":3,"file":"sprites-contract.d.ts","sourceRoot":"","sources":["../../../src/op/activities/sprites-contract.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,mDAAmD;AACnD,MAAM,WAAW,eAAe;IAC9B,mEAAmE;IACnE,MAAM,EAAE,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,QAAQ,GAAG,IAAI,CAAC;IACjD,gFAAgF;IAChF,IAAI,EAAE,MAAM,CAAC;IACb,kEAAkE;IAClE,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,gBAAgB,EAAE,SAAS,eAAe,EAwB7C,CAAC;AAEX;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAItE;AAED,8DAA8D;AAC9D,wBAAgB,YAAY,IAAI,GAAG,CAAC,MAAM,CAAC,CAE1C"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@intentius/chant-lexicon-fly",
3
- "version": "0.18.7",
3
+ "version": "0.18.9",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "src/",
@@ -57,7 +57,7 @@
57
57
  "typescript": "^5.9.3"
58
58
  },
59
59
  "peerDependencies": {
60
- "@intentius/chant": "^0.18.7"
60
+ "@intentius/chant": "^0.18.9"
61
61
  },
62
62
  "description": "Google Cloud lexicon for chant — declarative IaC in TypeScript",
63
63
  "license": "Apache-2.0",
@@ -18,4 +18,4 @@
18
18
  export const MUDFLAPS_IMAGE = "ghcr.io/intentius/mudflaps:0.4.0";
19
19
 
20
20
  /** Pinned spritzer (Fly Sprites emulator) image. */
21
- export const SPRITZER_IMAGE = "ghcr.io/intentius/spritzer:0.3.1";
21
+ export const SPRITZER_IMAGE = "ghcr.io/intentius/spritzer:0.4.0";
@@ -0,0 +1,98 @@
1
+ import { describe, test, expect, beforeAll, afterAll } from "vitest";
2
+ import { spritesUp, spritesDown } from "./sprites-emulator";
3
+ import { spriteCreate } from "./sprites";
4
+ import { spriteWriteFile, spriteReadFile, spriteListDir, spriteRemove } from "./sprite-fs";
5
+ import { spriteApplyNetworkPolicy, spriteApplyServices } from "./sprite-config";
6
+ import { spriteTaskCreate, spriteTaskRelease } from "./sprite-tasks";
7
+
8
+ // The filesystem / config-reconcile / keep-alive activities driven against the
9
+ // REAL spritzer image (ghcr.io/intentius/spritzer), booted via `spritesUp` — the
10
+ // twin of the lifecycle docker test. Proves the image serves these endpoints
11
+ // with the same wire shape the in-process fake does. Docker required;
12
+ // deterministically skipped in CI unless SPRITES_DOCKER=1.
13
+
14
+ const CONTAINER = "chant-spritzer-config-it";
15
+ const PORT = 4294;
16
+
17
+ let available = false;
18
+ let endpoint = "";
19
+
20
+ async function inspect(id: string): Promise<{
21
+ fs: Record<string, string>;
22
+ netPolicy: Array<{ domain: string; action: string }>;
23
+ services: Record<string, { state: { status: string } }>;
24
+ tasks: Record<string, unknown>;
25
+ }> {
26
+ const res = await fetch(`${endpoint}/v1/sprites/${id}`);
27
+ return (await res.json()) as never;
28
+ }
29
+
30
+ beforeAll(async () => {
31
+ if (process.env.CI && !process.env.SPRITES_DOCKER) {
32
+ available = false;
33
+ return;
34
+ }
35
+ try {
36
+ const up = await spritesUp({ name: CONTAINER, port: PORT, timeoutMs: 30_000 });
37
+ endpoint = up.endpoint;
38
+ available = true;
39
+ } catch {
40
+ available = false;
41
+ }
42
+ }, 60_000);
43
+
44
+ afterAll(async () => {
45
+ if (available) await spritesDown({ name: CONTAINER });
46
+ });
47
+
48
+ describe("fs / config / tasks activities against real spritzer", () => {
49
+ test("filesystem round-trips (write → read → list → remove)", async (ctx) => {
50
+ if (!available) ctx.skip();
51
+ await spriteCreate({ name: "d-fs", endpoint });
52
+ await spriteWriteFile({ id: "d-fs", path: "/work/input", content: "hello", endpoint });
53
+ expect((await spriteReadFile({ id: "d-fs", path: "/work/input", endpoint })).content).toBe("hello");
54
+ await spriteWriteFile({ id: "d-fs", path: "/work/notes/a", content: "x", endpoint });
55
+ const list = await spriteListDir({ id: "d-fs", path: "/work", endpoint });
56
+ expect(list.map((e) => e.name).sort()).toEqual(["input", "notes"]);
57
+ await spriteRemove({ id: "d-fs", path: "/work/input", endpoint });
58
+ await expect(spriteReadFile({ id: "d-fs", path: "/work/input", endpoint })).rejects.toThrow(/not found/);
59
+ });
60
+
61
+ test("network policy reconcile converges", async (ctx) => {
62
+ if (!available) ctx.skip();
63
+ await spriteCreate({ name: "d-np", endpoint });
64
+ const rules = [
65
+ { domain: "api.anthropic.com", action: "allow" as const },
66
+ { domain: "*", action: "deny" as const },
67
+ ];
68
+ expect((await spriteApplyNetworkPolicy({ id: "d-np", rules, endpoint })).changed).toBe(true);
69
+ expect((await spriteApplyNetworkPolicy({ id: "d-np", rules, endpoint })).changed).toBe(false);
70
+ expect((await inspect("d-np")).netPolicy).toEqual(rules);
71
+ });
72
+
73
+ test("services reconcile creates + starts in dependency order", async (ctx) => {
74
+ if (!available) ctx.skip();
75
+ await spriteCreate({ name: "d-svc", endpoint });
76
+ const r = await spriteApplyServices({
77
+ id: "d-svc",
78
+ start: true,
79
+ endpoint,
80
+ services: [
81
+ { name: "web", cmd: "run-web", needs: ["db"], http_port: 8080 },
82
+ { name: "db", cmd: "run-db" },
83
+ ],
84
+ });
85
+ expect(r.started).toEqual(["db", "web"]);
86
+ expect((await inspect("d-svc")).services.web.state.status).toBe("running");
87
+ });
88
+
89
+ test("keep-alive task holds then releases (idempotent)", async (ctx) => {
90
+ if (!available) ctx.skip();
91
+ await spriteCreate({ name: "d-task", endpoint });
92
+ await spriteTaskCreate({ id: "d-task", name: "session", expire: "5m", endpoint });
93
+ expect(Object.keys((await inspect("d-task")).tasks)).toEqual(["session"]);
94
+ await spriteTaskRelease({ id: "d-task", name: "session", endpoint });
95
+ expect(Object.keys((await inspect("d-task")).tasks)).toEqual([]);
96
+ await expect(spriteTaskRelease({ id: "d-task", name: "session", endpoint })).resolves.toBeDefined();
97
+ });
98
+ });
@@ -7,15 +7,29 @@ import { SPRITES_CONTRACT, normalizeEndpoint, contractKeys } from "./sprites-con
7
7
  describe("SPRITES_CONTRACT", () => {
8
8
  test("covers every sprite activity that calls the Sprites API", () => {
9
9
  const activities = new Set(SPRITES_CONTRACT.map((e) => e.activity));
10
- // The six ./sprites.ts activities that make an HTTP/WS call.
10
+ // Every activity across the lifecycle, filesystem, config-reconcile, and
11
+ // keep-alive modules that makes an HTTP/WS call.
11
12
  expect(activities).toEqual(
12
13
  new Set([
14
+ // lifecycle (./sprites.ts)
13
15
  "spriteCreate",
14
16
  "spriteExec",
15
17
  "spriteCheckpoint",
16
18
  "listCheckpoints",
17
19
  "spriteRestore",
18
20
  "spriteDestroy",
21
+ // filesystem (./sprite-fs.ts)
22
+ "spriteWriteFile",
23
+ "spriteReadFile",
24
+ "spriteListDir",
25
+ "spriteRemove",
26
+ // config reconcile (./sprite-config.ts)
27
+ "spriteApplyNetworkPolicy",
28
+ "spriteApplyServices",
29
+ // keep-alive tasks (./sprite-tasks.ts)
30
+ "spriteTaskCreate",
31
+ "spriteTaskRefresh",
32
+ "spriteTaskRelease",
19
33
  ]),
20
34
  );
21
35
  });
@@ -23,7 +37,7 @@ describe("SPRITES_CONTRACT", () => {
23
37
  test("every entry has a v1 path and a known method", () => {
24
38
  for (const e of SPRITES_CONTRACT) {
25
39
  expect(e.path.startsWith("/v1/sprites")).toBe(true);
26
- expect(["GET", "POST", "DELETE", "WS"]).toContain(e.method);
40
+ expect(["GET", "POST", "PUT", "DELETE", "WS"]).toContain(e.method);
27
41
  }
28
42
  });
29
43
 
@@ -43,18 +57,22 @@ describe("SPRITES_CONTRACT", () => {
43
57
  expect(keys.size).toBe(SPRITES_CONTRACT.length);
44
58
  });
45
59
 
46
- test("every contract path segment appears in the sprites.ts source (drift anchor)", () => {
60
+ test("every contract path segment appears in the activity source (drift anchor)", () => {
47
61
  // Anchors the hand-authored contract to the activity implementations: if an
48
62
  // activity's endpoint path changes, a segment goes missing here and the
49
- // contract must be updated in the same change.
50
- const src = readFileSync(join(dirname(fileURLToPath(import.meta.url)), "sprites.ts"), "utf-8");
63
+ // contract must be updated in the same change. Scans every module that owns
64
+ // contract endpoints, not just the lifecycle one.
65
+ const dir = dirname(fileURLToPath(import.meta.url));
66
+ const src = ["sprites.ts", "sprite-fs.ts", "sprite-config.ts", "sprite-tasks.ts"]
67
+ .map((f) => readFileSync(join(dir, f), "utf-8"))
68
+ .join("\n");
51
69
  const segments = new Set(
52
70
  SPRITES_CONTRACT.flatMap((e) =>
53
71
  e.path.split("/").filter((s) => s.length > 0 && !s.startsWith("{")),
54
72
  ),
55
73
  );
56
74
  for (const seg of segments) {
57
- expect(src, `path segment "${seg}" from the contract is absent from sprites.ts`).toContain(seg);
75
+ expect(src, `path segment "${seg}" from the contract is absent from the activity source`).toContain(seg);
58
76
  }
59
77
  });
60
78
  });
@@ -5,8 +5,8 @@
5
5
  * the fly resource surface generates + drift-checks against. The Sprites API
6
6
  * (api.sprites.dev) ships no such spec at any conventional path, so there is
7
7
  * nothing to diff automatically. This module is the manual stand-in: the exact
8
- * endpoint set the fly sprite activities (./sprites.ts) depend on, maintained by
9
- * hand from https://docs.sprites.dev.
8
+ * endpoint set the fly sprite activities (lifecycle, filesystem, config, tasks)
9
+ * depend on, maintained by hand from https://docs.sprites.dev.
10
10
  *
11
11
  * It anchors two fidelity checks:
12
12
  * 1. Coverage — every endpoint here must be served by the pinned spritzer
@@ -24,24 +24,45 @@
24
24
  /** One endpoint the fly sprite activities call. */
25
25
  export interface SpritesEndpoint {
26
26
  /** HTTP method, or "WS" for the control-WebSocket exec channel. */
27
- method: "GET" | "POST" | "DELETE" | "WS";
27
+ method: "GET" | "POST" | "PUT" | "DELETE" | "WS";
28
28
  /** Path template under the Sprites base, e.g. `/v1/sprites/{id}/checkpoint`. */
29
29
  path: string;
30
- /** The fly activity that calls it (./sprites.ts export). */
30
+ /** The fly activity that calls it (an activity module export). */
31
31
  activity: string;
32
32
  }
33
33
 
34
34
  /**
35
- * The Sprites endpoints ./sprites.ts depends on. Keep in sync with the activity
36
- * implementations the unit test asserts every sprite activity is represented.
35
+ * The Sprites endpoints the fly sprite activities depend on, across the lifecycle
36
+ * (./sprites.ts), filesystem (./sprite-fs.ts), config reconcile
37
+ * (./sprite-config.ts), and keep-alive tasks (./sprite-tasks.ts) modules. Keep in
38
+ * sync with the activity implementations — the unit test asserts every sprite
39
+ * activity is represented, and the coverage test asserts the pinned spritzer
40
+ * serves each one.
37
41
  */
38
42
  export const SPRITES_CONTRACT: readonly SpritesEndpoint[] = [
43
+ // Lifecycle (./sprites.ts)
39
44
  { method: "POST", path: "/v1/sprites", activity: "spriteCreate" },
40
45
  { method: "WS", path: "/v1/sprites/{id}/exec", activity: "spriteExec" },
41
46
  { method: "POST", path: "/v1/sprites/{id}/checkpoint", activity: "spriteCheckpoint" },
42
47
  { method: "GET", path: "/v1/sprites/{id}/checkpoints", activity: "listCheckpoints" },
43
48
  { method: "POST", path: "/v1/sprites/{id}/checkpoints/{cp}/restore", activity: "spriteRestore" },
44
49
  { method: "DELETE", path: "/v1/sprites/{id}", activity: "spriteDestroy" },
50
+ // Filesystem (./sprite-fs.ts)
51
+ { method: "PUT", path: "/v1/sprites/{id}/fs/write", activity: "spriteWriteFile" },
52
+ { method: "GET", path: "/v1/sprites/{id}/fs/read", activity: "spriteReadFile" },
53
+ { method: "GET", path: "/v1/sprites/{id}/fs/list", activity: "spriteListDir" },
54
+ { method: "DELETE", path: "/v1/sprites/{id}/fs/delete", activity: "spriteRemove" },
55
+ // Network policy (./sprite-config.ts)
56
+ { method: "GET", path: "/v1/sprites/{id}/policy/network", activity: "spriteApplyNetworkPolicy" },
57
+ { method: "POST", path: "/v1/sprites/{id}/policy/network", activity: "spriteApplyNetworkPolicy" },
58
+ // Services (./sprite-config.ts)
59
+ { method: "GET", path: "/v1/sprites/{id}/services", activity: "spriteApplyServices" },
60
+ { method: "PUT", path: "/v1/sprites/{id}/services/{svc}", activity: "spriteApplyServices" },
61
+ { method: "POST", path: "/v1/sprites/{id}/services/{svc}/start", activity: "spriteApplyServices" },
62
+ // Keep-alive tasks (./sprite-tasks.ts)
63
+ { method: "POST", path: "/v1/sprites/{id}/tasks", activity: "spriteTaskCreate" },
64
+ { method: "PUT", path: "/v1/sprites/{id}/tasks/{name}", activity: "spriteTaskRefresh" },
65
+ { method: "DELETE", path: "/v1/sprites/{id}/tasks/{name}", activity: "spriteTaskRelease" },
45
66
  ] as const;
46
67
 
47
68
  /**