@intentius/chant-lexicon-fly 0.93.0 → 0.95.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.
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Sprite Services activities (#2711) — imperative create/get/list/start/stop/
3
- * delete/logs over a Sprite's background services, the surface a box's door,
4
- * hud and chud run as long-lived services through (`sprite-env services`
3
+ * delete/logs over a Sprite's background services, the surface a box's door
4
+ * and hud run as long-lived services through (`sprite-env services`
5
5
  * inside the sprite, `/v1/sprites/{id}/services/...` outside). Shared by the
6
6
  * Sprites API, wisp (arugula-salad/wisp) and spritzer 0.6.0's container mode
7
7
  * (INTENTIUS/spritzer#22).
@@ -630,7 +630,7 @@ export interface SpriteUrlResult {
630
630
  * nothing until now exposed it on its own (for a later phase that only has
631
631
  * the sprite `id`) or waited on it. `GET /v1/sprites/{id}` for the URL; with
632
632
  * `path` set, polls `GET {url}{path}` until `status` (default any 2xx) or
633
- * `timeoutMs` runs out — the box's door/hud/chud services take a moment to
633
+ * `timeoutMs` runs out — the box's door/hud services take a moment to
634
634
  * bind their `http_port` after `spriteServiceStart`.
635
635
  */
636
636
  export async function spriteUrl(
@@ -13,7 +13,7 @@ import {
13
13
  } from "@intentius/chant/op";
14
14
  import * as contracts from "./activity-contracts";
15
15
  import * as activities from "./activities";
16
- import { spriteServicesObserve, spriteServiceRestart } from "./builders";
16
+ import { spriteApplyServices, spriteServicesObserve, spriteServiceRestart } from "./builders";
17
17
 
18
18
  const CONTRACTS: Map<string, ActivityContract> = new Map(
19
19
  Object.values(contracts).filter(isActivityContract).map((c) => [c.name, c]),
@@ -29,7 +29,7 @@ describe("fly activity contracts (#2843)", () => {
29
29
  expect(typeof (activities as unknown as Record<string, unknown>)[c.name], `${c.name} is not an exported activity`).toBe("function");
30
30
  expect(c.returns, `${c.name} declares no return schema`).toBeDefined();
31
31
  }
32
- expect([...CONTRACTS.keys()].sort()).toEqual(["spriteServiceRestart", "spriteServicesObserve"]);
32
+ expect([...CONTRACTS.keys()].sort()).toEqual(["spriteApplyServices", "spriteServiceRestart", "spriteServicesObserve"]);
33
33
  });
34
34
 
35
35
  test("a ConvergeOp observing spriteServicesObserve passes OPS012 and OPS013 (the guide's example)", () => {
@@ -63,9 +63,26 @@ describe("fly activity contracts (#2843)", () => {
63
63
  expect(issues.some((i) => i.message.includes("servicefile"))).toBe(true);
64
64
  });
65
65
 
66
+ test("the box forms validate: observe and restart with box: true, and an apply with box, only, start and restart (#2880)", () => {
67
+ const { op: converge } = ConvergeOp({
68
+ name: "converge",
69
+ env: "box",
70
+ observe: spriteServicesObserve({ box: true }),
71
+ rules: [when<ResourceSymptom>(eq("status", "drifted"), run("restart-service"), { id: "restart-drifted", why: "restart it" })],
72
+ });
73
+ expect(validateActivitySteps(converge.props as never, CONTRACTS)).toEqual([]);
74
+ expect(validateStepOutputRefs(converge.props as never, CONTRACTS)).toEqual([]);
75
+ expect(validateActivitySteps(op([spriteServiceRestart({ box: true })]), CONTRACTS)).toEqual([]);
76
+ expect(validateActivitySteps(op([spriteApplyServices({ box: true, only: ["site"], start: true, restart: true })]), CONTRACTS)).toEqual([]);
77
+ expect(validateActivitySteps(op([spriteApplyServices({ id: "box-1", services: [{ name: "app", cmd: "node app.js", needs: [] }], start: true })]), CONTRACTS)).toEqual([]);
78
+ const issues = validateActivitySteps(op([{ kind: "activity", fn: "spriteApplyServices", args: { box: true, onlyy: ["site"] } }]), CONTRACTS);
79
+ expect(issues.some((i) => i.message.includes("onlyy"))).toBe(true);
80
+ });
81
+
66
82
  test("loadActivityContracts finds them at @intentius/chant-lexicon-fly/op/activity-contracts", async () => {
67
83
  const loaded = await loadActivityContracts(["fly"]);
68
84
  expect(loaded.get("spriteServicesObserve")?.returns).toBeDefined();
69
85
  expect(loaded.get("spriteServiceRestart")?.returns).toBeDefined();
86
+ expect(loaded.get("spriteApplyServices")?.returns).toBeDefined();
70
87
  });
71
88
  });
@@ -12,12 +12,14 @@
12
12
  * needs its return schema; without one, every project declaring such a
13
13
  * ConvergeOp failed `chant build` and `chant lint`. `spriteServiceRestart` is
14
14
  * the step of the Op such a ConvergeOp's rule runs, and its result is what a
15
- * later step would read. The other Sprites activities have no contract yet,
15
+ * later step would read. `spriteApplyServices` is covered for its box form
16
+ * (#2880): `box`, `only`, `start` and `restart`, and the per-service actions
17
+ * its result names. The other Sprites activities have no contract yet,
16
18
  * which OPS012 skips rather than flags.
17
19
  *
18
20
  * Each `args` schema mirrors the `*Args` interface in
19
- * `./activities/sprite-service-converge.ts` and each `returns` schema the
20
- * `*Result`. Authored with `z.strictObject(...)`, so a misspelled key fails
21
+ * `./activities/sprite-service-converge.ts` or `./activities/sprite-config.ts`
22
+ * and each `returns` schema the `*Result`. Authored with `z.strictObject(...)`, so a misspelled key fails
21
23
  * the build instead of vanishing.
22
24
  */
23
25
 
@@ -44,6 +46,7 @@ export const spriteServicesObserveContract = activityContract(
44
46
  ...via,
45
47
  services: z.array(declaredService).optional(),
46
48
  servicesFile: z.string().optional(),
49
+ box: z.boolean().optional(),
47
50
  probes: z.number().optional(),
48
51
  probeIntervalMs: z.number().optional(),
49
52
  }),
@@ -61,7 +64,38 @@ export const spriteServiceRestartContract = activityContract(
61
64
  health: z.string().optional(),
62
65
  services: z.array(declaredService).optional(),
63
66
  servicesFile: z.string().optional(),
67
+ box: z.boolean().optional(),
64
68
  waitMs: z.number().optional(),
65
69
  }),
66
70
  z.object({ name: z.string(), healthy: z.boolean().nullable() }),
67
71
  );
72
+
73
+ const serviceSpec = z.strictObject({
74
+ name: z.string(),
75
+ cmd: z.string(),
76
+ args: z.array(z.string()).optional(),
77
+ env: z.record(z.string(), z.string()).optional(),
78
+ dir: z.string().optional(),
79
+ needs: z.array(z.string()).optional(),
80
+ http_port: z.number().optional(),
81
+ });
82
+
83
+ export const spriteApplyServicesContract = activityContract(
84
+ "spriteApplyServices",
85
+ z.strictObject({
86
+ id: z.string().optional(),
87
+ services: z.array(serviceSpec).optional(),
88
+ box: z.boolean().optional(),
89
+ only: z.array(z.string()).optional(),
90
+ start: z.boolean().optional(),
91
+ restart: z.boolean().optional(),
92
+ spriteEnv: z.string().optional(),
93
+ endpoint: z.string().optional(),
94
+ token: z.string().optional(),
95
+ }),
96
+ z.object({
97
+ applied: z.array(z.string()),
98
+ started: z.array(z.string()),
99
+ services: z.array(z.object({ name: z.string(), action: z.enum(["created", "replaced", "restarted", "started", "left"]) })).optional(),
100
+ }),
101
+ );
@@ -108,7 +108,13 @@ export const spriteListDir = spriteStep<SpriteListDirArgs>("spriteListDir", "fas
108
108
  export const spriteRemove = spriteStep<SpriteRemoveArgs>("spriteRemove", "fastIdempotent");
109
109
  /** Reconcile a sprite's outbound network policy — the fully typed twin of core's `spriteApplyNetworkPolicy`. Defaults to the `fastIdempotent` profile. */
110
110
  export const spriteApplyNetworkPolicy = spriteStep<SpriteApplyNetworkPolicyArgs>("spriteApplyNetworkPolicy", "fastIdempotent");
111
- /** Reconcile a sprite's background services — the fully typed twin of core's `spriteApplyServices`. Defaults to the `fastIdempotent` profile. */
111
+ /**
112
+ * Reconcile a sprite's background services — the fully typed twin of core's
113
+ * `spriteApplyServices`. With an `id`, through the Sprites API; without one,
114
+ * inside the sprite through sprite-env, applying the box block's services
115
+ * (`box: true`, with `only`, `start` and `restart`, #2880). Defaults to the
116
+ * `fastIdempotent` profile.
117
+ */
112
118
  export const spriteApplyServices = spriteStep<SpriteApplyServicesArgs>("spriteApplyServices", "fastIdempotent");
113
119
  /** Create-and-start one background service (#2711) — the single-service primitive underneath `spriteApplyServices`. Defaults to the `longInfra` profile (the create+start NDJSON round trip). */
114
120
  export const spriteServiceCreate = spriteStep<SpriteServiceCreateArgs>("spriteServiceCreate", "longInfra");
@@ -104,13 +104,13 @@ The same lexicon ships more Sprite primitives, all imported from `@intentius/cha
104
104
  | Family | Activities | Use |
105
105
  |--------|-----------|-----|
106
106
  | Filesystem (#848) | `spriteWriteFile` / `spriteReadFile` / `spriteListDir` / `spriteRemove` | stage an input file and read a result out without shelling `spriteExec` + `cat` |
107
- | Config reconcile (#849) | `spriteApplyNetworkPolicy` / `spriteApplyServices` | reconcile a Sprite's egress allowlist and background services against typed config (validated before any HTTP; a whole-object replace for policy, create-or-update by name for services) |
108
- | Services (#2711) | `spriteServiceCreate` / `spriteServiceGet` / `spriteServiceList` / `spriteServiceStart` / `spriteServiceStop` / `spriteServiceDelete` / `spriteServiceLogs` | the single-service primitives underneath `spriteApplyServices` — create-and-start one long-lived service (a box's door, hud or chud), inspect or list what's running, stop/start or delete one by name, read its log tail |
109
- | Converge (#2778) | `spriteServicesObserve` / `spriteServiceRestart` | keep a box's declared services running with a `ConvergeOp`: the observer step gives one verdict per service (`in-sync`, `drifted`, `unknown`) from the supervisor and each health URL, and the restart is the step of the Op a rule dispatches for the drifted service. Inside the sprite they call `sprite-env services`; with a sprite `id` they use the API |
107
+ | Config reconcile (#849) | `spriteApplyNetworkPolicy` / `spriteApplyServices` | reconcile a Sprite's egress allowlist and background services against typed config (validated before any HTTP; a whole-object replace for policy, create-or-update by name for services). Without an `id`, `spriteApplyServices({ box: true })` runs inside the sprite and applies the box block's `services` through `sprite-env services` (#2880): it creates the missing ones and deletes and creates the changed ones, in dependency order, with `only`, `start` and `restart` |
108
+ | Services (#2711) | `spriteServiceCreate` / `spriteServiceGet` / `spriteServiceList` / `spriteServiceStart` / `spriteServiceStop` / `spriteServiceDelete` / `spriteServiceLogs` | the single-service primitives underneath `spriteApplyServices` — create-and-start one long-lived service (a box's door or hud), inspect or list what's running, stop/start or delete one by name, read its log tail |
109
+ | Converge (#2778) | `spriteServicesObserve` / `spriteServiceRestart` | keep a box's declared services running with a `ConvergeOp`: the observer step gives one verdict per service (`in-sync`, `drifted`, `unknown`) from the supervisor and each health URL, and the restart is the step of the Op a rule dispatches for the drifted service. Inside the sprite they call `sprite-env services`; with a sprite `id` they use the API. The services come from `services`, a `servicesFile`, or with `box: true` the box block's `services` in `chant.workspace.json` (#2880) |
110
110
  | Sprite URL / delete (#2711) | `spriteUrl` / `spriteDelete` | resolve a Sprite's URL, optionally waiting until a path on it answers; delete the Sprite (`spriteDelete` is `spriteDestroy` under the name spritzer/wisp and the other lexicons' delete activities use — same call, either name) |
111
111
  | Keep-alive (#847) | `spriteTaskCreate` / `spriteTaskRefresh` / `spriteTaskRelease` | hold a Sprite active for a session so it will not pause; a session past the 1-hour task cap refreshes on an interval |
112
112
 
113
- These are still runtime-orchestration primitives, not declarable resources — a Sprite has no desired-state create body to reconcile. Services created directly with `spriteServiceCreate` are what a box's `spritzer` preset provisions door/hud/chud on (arugula-salad/studio#27) — the same wire surface as `sprite-env services` inside the sprite, spritzer 0.6.0's container mode, and wisp.
113
+ These are still runtime-orchestration primitives, not declarable resources — a Sprite has no desired-state create body to reconcile. Services created directly with `spriteServiceCreate` are what a box's `spritzer` preset provisions door/hud on (arugula-salad/studio#27) — the same wire surface as `sprite-env services` inside the sprite, spritzer 0.6.0's container mode, and wisp.
114
114
 
115
115
  ## Where it fits
116
116