@intentius/chant-lexicon-fly 0.124.30 → 0.124.32
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.
- package/dist/codegen/docs-cli.js +6 -0
- package/dist/codegen/docs.js +143 -0
- package/dist/codegen/generate-cli.js +26 -0
- package/dist/codegen/generate-lexicon.js +38 -0
- package/dist/codegen/generate-typescript.js +45 -0
- package/dist/codegen/generate.js +82 -0
- package/dist/codegen/naming.js +33 -0
- package/dist/codegen/package.js +35 -0
- package/dist/components/capability-plugin.js +26 -0
- package/dist/components/fly-release.js +335 -0
- package/dist/components/index.js +10 -0
- package/dist/components/run-agent.js +133 -0
- package/dist/composites/catalog.js +176 -0
- package/dist/composites/fly-deploy.js +91 -0
- package/dist/composites/fly-otel-collector.js +66 -0
- package/dist/composites/fly-site.js +80 -0
- package/dist/coverage.js +29 -0
- package/dist/describe-resources.js +443 -0
- package/dist/detect.js +63 -0
- package/dist/export-resources.js +81 -0
- package/dist/generated/index.js +44 -0
- package/dist/generated/runtime.js +4 -0
- package/dist/import/generator.js +208 -0
- package/dist/import/live-export.js +81 -0
- package/dist/import/parser.js +157 -0
- package/dist/index.js +40 -0
- package/dist/integrity.json +3 -3
- package/dist/lint/audit-catalog.js +28 -0
- package/dist/lint/audit-lineage.js +5 -0
- package/dist/lint/post-synth/fly-helpers.js +24 -0
- package/dist/lint/post-synth/fly010-machine-requires-image.js +33 -0
- package/dist/lint/post-synth/fly011-mount-references-declared-volume.js +58 -0
- package/dist/lint/post-synth/index.js +6 -0
- package/dist/lint/rules/guest-sizing.js +92 -0
- package/dist/lint/rules/index.js +12 -0
- package/dist/lint/rules/no-secret-literals.js +58 -0
- package/dist/lint/rules/valid-region.js +52 -0
- package/dist/lsp/completions.js +12 -0
- package/dist/lsp/hover.js +12 -0
- package/dist/manifest.json +1 -1
- package/dist/mcp/context-tools.js +113 -0
- package/dist/okf/types/Machine.md +1 -1
- package/dist/op/activities/box-services.js +88 -0
- package/dist/op/activities/emulator-images.js +32 -0
- package/dist/op/activities/flaps.js +34 -0
- package/dist/op/activities/fly-apply.js +911 -0
- package/dist/op/activities/fly-release-step.js +21 -0
- package/dist/op/activities/fly-rollback-step.js +22 -0
- package/dist/op/activities/index.js +53 -0
- package/dist/op/activities/machine-release.js +236 -0
- package/dist/op/activities/machines-contract.js +81 -0
- package/dist/op/activities/machines-fake.js +203 -0
- package/dist/op/activities/machines-local-cli.js +85 -0
- package/dist/op/activities/machines-local.js +395 -0
- package/dist/op/activities/progress.js +10 -0
- package/dist/op/activities/sprite-config.js +409 -0
- package/dist/op/activities/sprite-fs.js +131 -0
- package/dist/op/activities/sprite-service-converge.js +224 -0
- package/dist/op/activities/sprite-services.js +174 -0
- package/dist/op/activities/sprite-tasks.js +57 -0
- package/dist/op/activities/sprites-contract.js +87 -0
- package/dist/op/activities/sprites-emulator.js +72 -0
- package/dist/op/activities/sprites-fake.js +547 -0
- package/dist/op/activities/sprites.js +515 -0
- package/dist/op/activity-contracts.js +82 -0
- package/dist/op/builders.js +157 -0
- package/dist/ownership.js +29 -0
- package/dist/package-cli.js +20 -0
- package/dist/plugin.js +298 -0
- package/dist/pseudo.js +26 -0
- package/dist/reference-catalog.js +37 -0
- package/dist/release-metadata.js +59 -0
- package/dist/release-store.js +46 -0
- package/dist/serializer.js +299 -0
- package/dist/spec/fetch.js +32 -0
- package/dist/spec/parse.js +260 -0
- package/dist/teardown.js +194 -0
- package/dist/types/index.d.ts +33 -2
- package/dist/validate-cli.js +4 -0
- package/dist/validate.js +33 -0
- package/package.json +11 -11
- package/src/codegen/package.ts +2 -2
- package/src/generated/index.d.ts +33 -2
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typed step-builder wrappers for this lexicon's sprite activities (chant
|
|
3
|
+
* #1288 Stage 2 — "regenerate the step builders as fully typed wrappers").
|
|
4
|
+
* Core's own sprite builders (`spriteCreate`, `spriteExec`, ...) already
|
|
5
|
+
* carry an inline object-literal type in `packages/core/src/op/builders.ts`
|
|
6
|
+
* — but that inline type is a hand-restated MIRROR of this lexicon's own
|
|
7
|
+
* `Sprite*Args` interfaces (`./activities/sprites.ts`,
|
|
8
|
+
* `./activities/sprite-fs.ts`, `./activities/sprite-tasks.ts`,
|
|
9
|
+
* `./activities/sprite-config.ts`, `./activities/sprites-emulator.ts`),
|
|
10
|
+
* exactly the duplication Stage 2 exists to eliminate. `opts`'s type in
|
|
11
|
+
* every wrapper below IS the activity's own `*Args` interface (via
|
|
12
|
+
* `WithStepRefs`) — never restated — so it stays in sync with the
|
|
13
|
+
* implementation by construction.
|
|
14
|
+
*
|
|
15
|
+
* There's no cross-package layering problem here (unlike `kubectlApply`/
|
|
16
|
+
* `helmInstall`): this package already owns both the activities and (per
|
|
17
|
+
* `./composites/fly-deploy.ts`'s existing `flapsUp`/`flapsDown`/
|
|
18
|
+
* `flyApplyStep` precedent, #744) the step-builder layer. So — matching how
|
|
19
|
+
* `packages/core/src/op/builders.ts` handles ITS OWN activities — this
|
|
20
|
+
* module REPLACES (not adds alongside) the sprite builders this package's
|
|
21
|
+
* `src/index.ts` used to re-export from `@intentius/chant/op`: same names,
|
|
22
|
+
* same import path (`@intentius/chant-lexicon-fly`), so an existing
|
|
23
|
+
* `import { spriteCreate } from "@intentius/chant-lexicon-fly"` call site
|
|
24
|
+
* gains authoring-time types and `StepOutputRef`/`.out` support with no
|
|
25
|
+
* change. `core`'s own originals are untouched, for anyone still importing
|
|
26
|
+
* `@intentius/chant/op` directly.
|
|
27
|
+
*/
|
|
28
|
+
import { activity } from "@intentius/chant/op";
|
|
29
|
+
/**
|
|
30
|
+
* Build one whole-args typed sprite step builder — `args` IS the activity's
|
|
31
|
+
* own `*Args` type, `profile` routed off it, never restated. Only `profile`
|
|
32
|
+
* is extracted, deliberately NOT a step-authoring `id` the way the other
|
|
33
|
+
* lexicons' wrappers offer: `id` is itself a REQUIRED domain field on nearly
|
|
34
|
+
* every `Sprite*Args` here (the target sprite's id — `spriteExec`,
|
|
35
|
+
* `spriteWriteFile`, `spriteTaskCreate`, ...), so stripping an `id` key off
|
|
36
|
+
* the flat args object the way `takeProfileAndId` does elsewhere would
|
|
37
|
+
* silently steal the sprite id into the step's `id` and drop it from `args`
|
|
38
|
+
* — the exact silent-wrong-value failure class chant #1288 exists to catch,
|
|
39
|
+
* not reproduce. `.out`-by-id (#1290) is out of scope for the sprite family
|
|
40
|
+
* through this convenience layer, exactly as it was through core's original
|
|
41
|
+
* builders; an author who needs it authors `activity("spriteExec", args, {
|
|
42
|
+
* id: "..." })` directly. `Args` is deliberately unconstrained: a named
|
|
43
|
+
* interface without an explicit index signature (every `Sprite*Args` here)
|
|
44
|
+
* is not assignable to `Record<string, unknown>` structurally, even though
|
|
45
|
+
* every field it does declare is — the `as Record<string, unknown>` cast
|
|
46
|
+
* below is a type ASSERTION (permissive), not an assignment, so it doesn't
|
|
47
|
+
* need the constraint.
|
|
48
|
+
*/
|
|
49
|
+
function spriteStep(fn, defaultProfile) {
|
|
50
|
+
return (args) => {
|
|
51
|
+
const { profile, ...rest } = args;
|
|
52
|
+
return activity(fn, rest, profile ?? defaultProfile);
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
/** Create a sprite — the fully typed twin of core's `spriteCreate`. Defaults to the `longInfra` profile. */
|
|
56
|
+
export const spriteCreate = spriteStep("spriteCreate", "longInfra");
|
|
57
|
+
/** Run a command in a sprite — the fully typed twin of core's `spriteExec`. Defaults to the `longInfra` profile. */
|
|
58
|
+
export const spriteExec = spriteStep("spriteExec", "longInfra");
|
|
59
|
+
/** Checkpoint a sprite — the fully typed twin of core's `spriteCheckpoint`. Defaults to the `longInfra` profile. */
|
|
60
|
+
export const spriteCheckpoint = spriteStep("spriteCheckpoint", "longInfra");
|
|
61
|
+
/** Restore a sprite — the fully typed twin of core's `spriteRestore`. Defaults to the `longInfra` profile. */
|
|
62
|
+
export const spriteRestore = spriteStep("spriteRestore", "longInfra");
|
|
63
|
+
/** List a sprite's checkpoints — the fully typed twin of core's `listCheckpoints`. Defaults to the `fastIdempotent` profile. */
|
|
64
|
+
export const listCheckpoints = spriteStep("listCheckpoints", "fastIdempotent");
|
|
65
|
+
/** Destroy a sprite — the fully typed twin of core's `spriteDestroy`. Defaults to the `fastIdempotent` profile. */
|
|
66
|
+
export const spriteDestroy = spriteStep("spriteDestroy", "fastIdempotent");
|
|
67
|
+
/** Delete a sprite (#2711; alias of `spriteDestroy`). Defaults to the `fastIdempotent` profile. */
|
|
68
|
+
export const spriteDelete = spriteStep("spriteDelete", "fastIdempotent");
|
|
69
|
+
/** Resolve a sprite's URL, optionally waiting until a path on it answers (#2711). Defaults to the `fastIdempotent` profile. */
|
|
70
|
+
export const spriteUrl = spriteStep("spriteUrl", "fastIdempotent");
|
|
71
|
+
/** Write a file into a sprite — the fully typed twin of core's `spriteWriteFile`. Defaults to the `fastIdempotent` profile. */
|
|
72
|
+
export const spriteWriteFile = spriteStep("spriteWriteFile", "fastIdempotent");
|
|
73
|
+
/** Read a file from a sprite — the fully typed twin of core's `spriteReadFile`. Defaults to the `fastIdempotent` profile. */
|
|
74
|
+
export const spriteReadFile = spriteStep("spriteReadFile", "fastIdempotent");
|
|
75
|
+
/** List a directory in a sprite — the fully typed twin of core's `spriteListDir`. Defaults to the `fastIdempotent` profile. */
|
|
76
|
+
export const spriteListDir = spriteStep("spriteListDir", "fastIdempotent");
|
|
77
|
+
/** Remove a path in a sprite — the fully typed twin of core's `spriteRemove`. Defaults to the `fastIdempotent` profile. */
|
|
78
|
+
export const spriteRemove = spriteStep("spriteRemove", "fastIdempotent");
|
|
79
|
+
/** Reconcile a sprite's outbound network policy — the fully typed twin of core's `spriteApplyNetworkPolicy`. Defaults to the `fastIdempotent` profile. */
|
|
80
|
+
export const spriteApplyNetworkPolicy = spriteStep("spriteApplyNetworkPolicy", "fastIdempotent");
|
|
81
|
+
/**
|
|
82
|
+
* Reconcile a sprite's background services — the fully typed twin of core's
|
|
83
|
+
* `spriteApplyServices`. With an `id`, through the Sprites API; without one,
|
|
84
|
+
* inside the sprite through sprite-env, applying the box block's services
|
|
85
|
+
* (`box: true`, with `only`, `start` and `restart`, #2880). Defaults to the
|
|
86
|
+
* `fastIdempotent` profile.
|
|
87
|
+
*/
|
|
88
|
+
export const spriteApplyServices = spriteStep("spriteApplyServices", "fastIdempotent");
|
|
89
|
+
/** Create-and-start one background service (#2711) — the single-service primitive underneath `spriteApplyServices`. Defaults to the `longInfra` profile (the create+start NDJSON round trip). */
|
|
90
|
+
export const spriteServiceCreate = spriteStep("spriteServiceCreate", "longInfra");
|
|
91
|
+
/** Get one background service's definition and live state (#2711). Defaults to the `fastIdempotent` profile. */
|
|
92
|
+
export const spriteServiceGet = spriteStep("spriteServiceGet", "fastIdempotent");
|
|
93
|
+
/** List a sprite's background services (#2711). Defaults to the `fastIdempotent` profile. */
|
|
94
|
+
export const spriteServiceList = spriteStep("spriteServiceList", "fastIdempotent");
|
|
95
|
+
/** Start a stopped background service (#2711). Defaults to the `longInfra` profile (the NDJSON round trip). */
|
|
96
|
+
export const spriteServiceStart = spriteStep("spriteServiceStart", "longInfra");
|
|
97
|
+
/** Stop a running background service (#2711). Defaults to the `fastIdempotent` profile. */
|
|
98
|
+
export const spriteServiceStop = spriteStep("spriteServiceStop", "fastIdempotent");
|
|
99
|
+
/** Delete a background service, idempotent (#2711). Defaults to the `fastIdempotent` profile. */
|
|
100
|
+
export const spriteServiceDelete = spriteStep("spriteServiceDelete", "fastIdempotent");
|
|
101
|
+
/** Read a background service's log tail (#2711). Defaults to the `fastIdempotent` profile. */
|
|
102
|
+
export const spriteServiceLogs = spriteStep("spriteServiceLogs", "fastIdempotent");
|
|
103
|
+
/**
|
|
104
|
+
* Observe a box's declared services for a ConvergeOp (#2778): one verdict per
|
|
105
|
+
* service, `in-sync`, `drifted` or `unknown`, from the supervisor's list and
|
|
106
|
+
* each service's health URL. Pass it as `ConvergeOp({ observe })`. Defaults
|
|
107
|
+
* to the `fastIdempotent` profile.
|
|
108
|
+
*/
|
|
109
|
+
export const spriteServicesObserve = spriteStep("spriteServicesObserve", "fastIdempotent");
|
|
110
|
+
/**
|
|
111
|
+
* Restart one service through its supervisor and wait for its health URL
|
|
112
|
+
* (#2778). Without `name`, the service is the one a ConvergeOp rule
|
|
113
|
+
* dispatched the run for. Defaults to the `longInfra` profile.
|
|
114
|
+
*/
|
|
115
|
+
export const spriteServiceRestart = spriteStep("spriteServiceRestart", "longInfra");
|
|
116
|
+
/** Create a keep-alive task — the fully typed twin of core's `spriteTaskCreate`. Defaults to the `fastIdempotent` profile. */
|
|
117
|
+
export const spriteTaskCreate = spriteStep("spriteTaskCreate", "fastIdempotent");
|
|
118
|
+
/** Refresh a keep-alive task's expiry — the fully typed twin of core's `spriteTaskRefresh`. Defaults to the `fastIdempotent` profile. */
|
|
119
|
+
export const spriteTaskRefresh = spriteStep("spriteTaskRefresh", "fastIdempotent");
|
|
120
|
+
/** Release a keep-alive task — the fully typed twin of core's `spriteTaskRelease`. Defaults to the `fastIdempotent` profile. */
|
|
121
|
+
export const spriteTaskRelease = spriteStep("spriteTaskRelease", "fastIdempotent");
|
|
122
|
+
/** Boot a local spritzer (Fly Sprites API emulator) — the fully typed twin of core's `spritesUp`. Defaults to the `longInfra` profile. */
|
|
123
|
+
export const spritesUp = (args = {}) => spriteStep("spritesUp", "longInfra")(args);
|
|
124
|
+
/** Stop and remove the local spritzer container — the fully typed twin of core's `spritesDown`. Defaults to the `fastIdempotent` profile. */
|
|
125
|
+
export const spritesDown = (args = {}) => spriteStep("spritesDown", "fastIdempotent")(args);
|
|
126
|
+
// ── Machines release activities (#2736, ws-056) ──────────────────────────────
|
|
127
|
+
// The site steps as Op steps: upload and start, a migration inside the
|
|
128
|
+
// Machine, restart, stop, verify and restore. Wrap a migration's
|
|
129
|
+
// `flyMachineExec` in `effect()` so it fires once per environment.
|
|
130
|
+
/** Apply the plan with the Machine serving a release (its digest in the Machine's metadata). Defaults to the `longInfra` profile. */
|
|
131
|
+
export const flyMachineRelease = spriteStep("flyMachineRelease", "longInfra");
|
|
132
|
+
/** Run a command inside a Machine (a migration). Defaults to the `atMostOnce` profile: chant cannot know the command is safe to repeat. */
|
|
133
|
+
export const flyMachineExec = spriteStep("flyMachineExec", "atMostOnce");
|
|
134
|
+
/** Restart a Machine under a lease and wait for it to be started. Defaults to the `longInfra` profile. */
|
|
135
|
+
export const flyMachineRestart = spriteStep("flyMachineRestart", "longInfra");
|
|
136
|
+
/** Stop a Machine under a lease. Defaults to the `longInfra` profile. */
|
|
137
|
+
export const flyMachineStop = spriteStep("flyMachineStop", "longInfra");
|
|
138
|
+
/** Check a Machine is started with a release, and its health endpoint answers. Defaults to the `longInfra` profile. */
|
|
139
|
+
export const flyMachineVerify = spriteStep("flyMachineVerify", "longInfra");
|
|
140
|
+
/** Put a recorded Machine config back (restore, rollback). Defaults to the `longInfra` profile. */
|
|
141
|
+
export const flyMachineRestore = spriteStep("flyMachineRestore", "longInfra");
|
|
142
|
+
/**
|
|
143
|
+
* Ship a release to a Fly Machine from an Op (#2782): the `fly-release`
|
|
144
|
+
* capability's steps (upload and start, each migration once per environment,
|
|
145
|
+
* verify, restore on failure), for the environment `environment` names. With `source`
|
|
146
|
+
* it puts an approved source tree on the Machine. Defaults to the `longInfra`
|
|
147
|
+
* profile.
|
|
148
|
+
*/
|
|
149
|
+
export const flyRelease = spriteStep("flyRelease", "longInfra");
|
|
150
|
+
/**
|
|
151
|
+
* Put an earlier release back on a Fly Machine from an Op (#2800): the
|
|
152
|
+
* `fly-rollback` capability's restore of the Machine config recorded for `to`
|
|
153
|
+
* in `environment`, then its verify. With `source` the archive is checked
|
|
154
|
+
* against its digest, and the recorded config against the archive's tree,
|
|
155
|
+
* before the Machine changes. Defaults to the `longInfra` profile.
|
|
156
|
+
*/
|
|
157
|
+
export const flyRollback = spriteStep("flyRollback", "longInfra");
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The fly lexicon's ownership convention (#743, decision D2), declared here so
|
|
3
|
+
* core carries no fly-specific branch — the #686 seam: each lexicon owns its key
|
|
4
|
+
* names; core keeps only the generic stamp/detect logic
|
|
5
|
+
* (@intentius/chant/ownership), the same way aws declares `AWS_TAG_OWNERSHIP_KEYS`
|
|
6
|
+
* and azure `AZURE_TAG_OWNERSHIP_KEYS`.
|
|
7
|
+
*
|
|
8
|
+
* The convention is two-tier, because flaps resources differ in what they can
|
|
9
|
+
* carry:
|
|
10
|
+
*
|
|
11
|
+
* 1. Primary marker — machine `config.metadata`. Machines carry a flat
|
|
12
|
+
* `Record<string, string>`, so chant stamps `managed-by: chant` (+ stack/env)
|
|
13
|
+
* there. This is the k8s-label / AWS-tag analogue and the basis for
|
|
14
|
+
* owned-only machine prune (a metadata filter — a foreign machine survives).
|
|
15
|
+
* The serializer stamps it (see serializer.ts); the applier reads it via
|
|
16
|
+
* `isChantOwned` → core `hasOwnershipMarker(..., FLY_METADATA_OWNERSHIP_KEYS)`.
|
|
17
|
+
*
|
|
18
|
+
* 2. Secondary boundary — the app. Volumes, secrets, certs, and IPs carry no
|
|
19
|
+
* arbitrary metadata, so for them the Fly app is the ownership boundary
|
|
20
|
+
* (like a CloudFormation stack): chant owns the app's declared-type
|
|
21
|
+
* resources and prunes owned orphans within app scope. See the app-scoped
|
|
22
|
+
* prune block in op/activities/fly-apply.ts for the limitation this leaves.
|
|
23
|
+
*/
|
|
24
|
+
/** Machine `config.metadata` keys for chant's ownership markers (tier 1, D2). */
|
|
25
|
+
export const FLY_METADATA_OWNERSHIP_KEYS = {
|
|
26
|
+
managedBy: "managed-by",
|
|
27
|
+
stack: "chant-stack",
|
|
28
|
+
env: "chant-env",
|
|
29
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
#!/usr/bin/env tsx
|
|
2
|
+
/**
|
|
3
|
+
* Thin entry point for `npm run bundle` in lexicon-fly.
|
|
4
|
+
* Generates src/generated/ files and writes dist/ bundle.
|
|
5
|
+
*
|
|
6
|
+
* NOTE: Uses top-level await (matching AWS/Azure pattern) to avoid
|
|
7
|
+
* event loop references from async wrappers keeping the process alive.
|
|
8
|
+
*/
|
|
9
|
+
import { packageLexicon } from "./codegen/package.js";
|
|
10
|
+
import { writeBundleSpec } from "@intentius/chant/codegen/package";
|
|
11
|
+
import { dirname, join } from "path";
|
|
12
|
+
import { fileURLToPath } from "url";
|
|
13
|
+
const pkgDir = dirname(fileURLToPath(import.meta.url));
|
|
14
|
+
const distDir = join(pkgDir, "..", "dist");
|
|
15
|
+
const verbose = process.argv.includes("--verbose") || !process.argv.includes("--quiet");
|
|
16
|
+
const force = process.argv.includes("--force");
|
|
17
|
+
const { spec, stats } = await packageLexicon({ verbose, force });
|
|
18
|
+
writeBundleSpec(spec, distDir);
|
|
19
|
+
console.error(`Packaged ${stats.resources} resources, ${stats.ruleCount} rules, ${stats.skillCount} skills`);
|
|
20
|
+
console.error(`dist/ written to ${distDir}`);
|
package/dist/plugin.js
ADDED
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
import { compositeCatalog } from "./composites/catalog.js";
|
|
2
|
+
import { createSkillsLoader } from "@intentius/chant/lexicon-plugin-helpers";
|
|
3
|
+
import { MUDFLAPS_EMULATOR } from "./op/activities/flaps.js";
|
|
4
|
+
import { SPRITZER_EMULATOR } from "./op/activities/sprites-emulator.js";
|
|
5
|
+
import { flyAuditCatalog } from "./lint/audit-catalog.js";
|
|
6
|
+
import { flySerializer } from "./serializer.js";
|
|
7
|
+
import { rules } from "./lint/rules/index.js";
|
|
8
|
+
import { postSynthChecks } from "./lint/post-synth/index.js";
|
|
9
|
+
import { completions } from "./lsp/completions.js";
|
|
10
|
+
import { hover } from "./lsp/hover.js";
|
|
11
|
+
import { detectTemplate as detectFlyTemplate } from "./detect.js";
|
|
12
|
+
import { FlyParser } from "./import/parser.js";
|
|
13
|
+
import { FlyGenerator } from "./import/generator.js";
|
|
14
|
+
import { flyReferenceCatalog } from "./reference-catalog.js";
|
|
15
|
+
import { flyContextTools } from "./mcp/context-tools.js";
|
|
16
|
+
import { FLY_METADATA_OWNERSHIP_KEYS } from "./ownership.js";
|
|
17
|
+
/**
|
|
18
|
+
* fly lexicon plugin.
|
|
19
|
+
*
|
|
20
|
+
* Implements all required LexiconPlugin lifecycle methods.
|
|
21
|
+
*/
|
|
22
|
+
export const flyPlugin = {
|
|
23
|
+
name: "fly",
|
|
24
|
+
ownershipChannel: { keys: FLY_METADATA_OWNERSHIP_KEYS, reads: ["describeResources"] },
|
|
25
|
+
// Two: the Machines API and Sprites (#1345).
|
|
26
|
+
emulator: [MUDFLAPS_EMULATOR, SPRITZER_EMULATOR],
|
|
27
|
+
auditCatalog: () => flyAuditCatalog,
|
|
28
|
+
serializer: flySerializer,
|
|
29
|
+
// ── Required lifecycle methods ────────────────────────────────
|
|
30
|
+
async generate(options) {
|
|
31
|
+
const { generate } = await import("./codegen/generate.js");
|
|
32
|
+
await generate(options);
|
|
33
|
+
},
|
|
34
|
+
async validate(options) {
|
|
35
|
+
const { validate } = await import("./validate.js");
|
|
36
|
+
const { printValidationResult } = await import("@intentius/chant/codegen/validate");
|
|
37
|
+
const result = await validate();
|
|
38
|
+
printValidationResult(result);
|
|
39
|
+
},
|
|
40
|
+
async coverage(options) {
|
|
41
|
+
const { analyzeFlyCoverage } = await import("./coverage.js");
|
|
42
|
+
await analyzeFlyCoverage({ verbose: options?.verbose, minOverall: options?.minOverall });
|
|
43
|
+
},
|
|
44
|
+
async package(options) {
|
|
45
|
+
const { packageLexicon } = await import("./codegen/package.js");
|
|
46
|
+
const { writeBundleSpec } = await import("@intentius/chant/codegen/package");
|
|
47
|
+
const { join, dirname } = await import("path");
|
|
48
|
+
const { fileURLToPath } = await import("url");
|
|
49
|
+
const { spec, stats } = await packageLexicon(options);
|
|
50
|
+
const pkgDir = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
51
|
+
writeBundleSpec(spec, join(pkgDir, "dist"));
|
|
52
|
+
console.error(`Packaged ${stats.resources} resources, ${stats.ruleCount} rules, ${stats.skillCount} skills`);
|
|
53
|
+
},
|
|
54
|
+
// ── Optional extensions ────────────────────────────────────
|
|
55
|
+
lintRules() {
|
|
56
|
+
return rules;
|
|
57
|
+
},
|
|
58
|
+
postSynthChecks() {
|
|
59
|
+
return postSynthChecks;
|
|
60
|
+
},
|
|
61
|
+
skills: createSkillsLoader(import.meta.url, [
|
|
62
|
+
{
|
|
63
|
+
file: "chant-fly.md",
|
|
64
|
+
name: "chant-fly",
|
|
65
|
+
description: "Author, lint, and deploy Fly apps and machines from a chant project, applied straight to the Machines API",
|
|
66
|
+
triggers: [
|
|
67
|
+
{ type: "file-pattern", value: "*.fly.ts" },
|
|
68
|
+
{ type: "context", value: "fly" },
|
|
69
|
+
{ type: "context", value: "fly.io" },
|
|
70
|
+
],
|
|
71
|
+
parameters: [
|
|
72
|
+
{
|
|
73
|
+
name: "resourceType",
|
|
74
|
+
type: "string",
|
|
75
|
+
description: "Fly resource type to work with (App, Machine, Volume, IPAddress, Certificate, Secret)",
|
|
76
|
+
},
|
|
77
|
+
],
|
|
78
|
+
examples: [
|
|
79
|
+
{
|
|
80
|
+
title: "Author an App and a Machine",
|
|
81
|
+
output: "new App({ name: \"my-app\", org_slug: Fly.OrgSlug });\nnew Machine({ region: \"iad\", config: new MachineConfig({ image: \"flyio/hellofly:latest\" }) })",
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
title: "Deploy against mudflaps offline",
|
|
85
|
+
input: "Apply the App + Machine without a Fly account",
|
|
86
|
+
output: "cd examples/local-fly && chant run fly",
|
|
87
|
+
},
|
|
88
|
+
],
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
file: "chant-fly-patterns.md",
|
|
92
|
+
name: "chant-fly-patterns",
|
|
93
|
+
description: "Volumes and mounts, IP assignments, certificates, apply-only secrets, and the app-boundary ownership model for Fly",
|
|
94
|
+
triggers: [
|
|
95
|
+
{ type: "context", value: "fly volumes" },
|
|
96
|
+
{ type: "context", value: "fly secrets" },
|
|
97
|
+
{ type: "context", value: "fly certificates" },
|
|
98
|
+
],
|
|
99
|
+
parameters: [],
|
|
100
|
+
examples: [
|
|
101
|
+
{
|
|
102
|
+
title: "Machine with a mounted volume",
|
|
103
|
+
input: "Attach a volume to a machine",
|
|
104
|
+
output: "new Volume({ name: \"data\", region: \"iad\", size_gb: 10 });\nnew Machine({ config: new MachineConfig({ mounts: [{ volume: \"data\", path: \"/data\" }] }) })",
|
|
105
|
+
},
|
|
106
|
+
],
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
file: "chant-fly-ops.md",
|
|
110
|
+
name: "chant-fly-ops",
|
|
111
|
+
description: "Operate a live Fly deploy — wait on stuck machines, resolve lease conflicts, prune safely, and target a real org versus the emulator",
|
|
112
|
+
triggers: [
|
|
113
|
+
{ type: "context", value: "fly apply" },
|
|
114
|
+
{ type: "context", value: "fly prune" },
|
|
115
|
+
{ type: "context", value: "flaps lease" },
|
|
116
|
+
],
|
|
117
|
+
parameters: [],
|
|
118
|
+
examples: [
|
|
119
|
+
{
|
|
120
|
+
title: "Deploy to a real Fly org",
|
|
121
|
+
input: "Point the same code at a real org",
|
|
122
|
+
output: "export FLY_API_TOKEN=... && chant run fly",
|
|
123
|
+
},
|
|
124
|
+
],
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
file: "chant-fly-sprites.md",
|
|
128
|
+
name: "chant-fly-sprites",
|
|
129
|
+
description: "Run an agent task in a Sprite as a chant Op — create, exec, checkpoint, restore, destroy, with checkpoint-as-compensation",
|
|
130
|
+
triggers: [
|
|
131
|
+
{ type: "context", value: "sprite" },
|
|
132
|
+
{ type: "context", value: "sprites.dev" },
|
|
133
|
+
{ type: "context", value: "agent sandbox" },
|
|
134
|
+
{ type: "context", value: "checkpoint compensation" },
|
|
135
|
+
],
|
|
136
|
+
parameters: [],
|
|
137
|
+
examples: [],
|
|
138
|
+
},
|
|
139
|
+
]),
|
|
140
|
+
composites() {
|
|
141
|
+
return compositeCatalog;
|
|
142
|
+
},
|
|
143
|
+
mcpTools() {
|
|
144
|
+
return flyContextTools();
|
|
145
|
+
},
|
|
146
|
+
mcpResources() {
|
|
147
|
+
// No standing MCP resources: Fly's read-only context is the fly:* tools above
|
|
148
|
+
// (build-from-source) plus the chant-fly skills. There's no static catalog or
|
|
149
|
+
// per-project artifact to expose as a resource.
|
|
150
|
+
return [];
|
|
151
|
+
},
|
|
152
|
+
detectTemplate(data) {
|
|
153
|
+
return detectFlyTemplate(data);
|
|
154
|
+
},
|
|
155
|
+
templateParser() {
|
|
156
|
+
return new FlyParser();
|
|
157
|
+
},
|
|
158
|
+
templateGenerator() {
|
|
159
|
+
return new FlyGenerator();
|
|
160
|
+
},
|
|
161
|
+
pseudoParameters() {
|
|
162
|
+
return [
|
|
163
|
+
"Fly::Region",
|
|
164
|
+
"Fly::OrgSlug",
|
|
165
|
+
"Fly::AppName",
|
|
166
|
+
];
|
|
167
|
+
},
|
|
168
|
+
initTemplates(template) {
|
|
169
|
+
if (template === "volume") {
|
|
170
|
+
return {
|
|
171
|
+
src: {
|
|
172
|
+
"infra.ts": `import { App, Machine, MachineConfig, MachineGuest, MachineMount, Volume, Fly } from "@intentius/chant-lexicon-fly";
|
|
173
|
+
|
|
174
|
+
// An app, a persistent volume, and a machine that mounts it. The serializer
|
|
175
|
+
// (#738) turns these into the flaps create bodies flyApply POSTs. The region
|
|
176
|
+
// comes from Fly.Region (resolved from FLY_REGION, defaulting to "iad").
|
|
177
|
+
const app = new App({ name: "my-app", org_slug: Fly.OrgSlug });
|
|
178
|
+
|
|
179
|
+
const data = new Volume({
|
|
180
|
+
name: "data",
|
|
181
|
+
region: Fly.Region,
|
|
182
|
+
size_gb: 10,
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
const web = new Machine({
|
|
186
|
+
name: "web",
|
|
187
|
+
region: Fly.Region,
|
|
188
|
+
config: new MachineConfig({
|
|
189
|
+
image: "flyio/hellofly:latest",
|
|
190
|
+
guest: new MachineGuest({ cpu_kind: "shared", cpus: 1, memory_mb: 256 }),
|
|
191
|
+
mounts: [new MachineMount({ volume: "data", path: "/data" })],
|
|
192
|
+
}),
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
export { app, data, web };
|
|
196
|
+
`,
|
|
197
|
+
},
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
if (template === "service") {
|
|
201
|
+
return {
|
|
202
|
+
src: {
|
|
203
|
+
"infra.ts": `import { App, Machine, MachineConfig, MachineGuest, MachineService, MachinePort, Fly } from "@intentius/chant-lexicon-fly";
|
|
204
|
+
|
|
205
|
+
// An app and a machine exposing a public HTTPS service on port 443 to internal
|
|
206
|
+
// 8080. The region comes from Fly.Region (resolved from FLY_REGION, defaulting
|
|
207
|
+
// to "iad").
|
|
208
|
+
const app = new App({ name: "my-app", org_slug: Fly.OrgSlug });
|
|
209
|
+
|
|
210
|
+
const web = new Machine({
|
|
211
|
+
name: "web",
|
|
212
|
+
region: Fly.Region,
|
|
213
|
+
config: new MachineConfig({
|
|
214
|
+
image: "flyio/hellofly:latest",
|
|
215
|
+
guest: new MachineGuest({ cpu_kind: "shared", cpus: 1, memory_mb: 256 }),
|
|
216
|
+
services: [
|
|
217
|
+
new MachineService({
|
|
218
|
+
protocol: "tcp",
|
|
219
|
+
internal_port: 8080,
|
|
220
|
+
ports: [new MachinePort({ port: 443, handlers: ["tls", "http"] })],
|
|
221
|
+
}),
|
|
222
|
+
],
|
|
223
|
+
}),
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
export { app, web };
|
|
227
|
+
`,
|
|
228
|
+
},
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
// Default: one app and one machine — the smallest complete deploy.
|
|
232
|
+
return {
|
|
233
|
+
src: {
|
|
234
|
+
"infra.ts": `import { App, Machine, MachineConfig, MachineGuest, Fly } from "@intentius/chant-lexicon-fly";
|
|
235
|
+
|
|
236
|
+
// One Fly app and one machine — the smallest complete deploy. The serializer
|
|
237
|
+
// (#738) turns these into the flaps create bodies flyApply POSTs. The region
|
|
238
|
+
// comes from Fly.Region (resolved from FLY_REGION, defaulting to "iad").
|
|
239
|
+
const app = new App({ name: "my-app", org_slug: Fly.OrgSlug });
|
|
240
|
+
|
|
241
|
+
const web = new Machine({
|
|
242
|
+
name: "web",
|
|
243
|
+
region: Fly.Region,
|
|
244
|
+
config: new MachineConfig({
|
|
245
|
+
image: "flyio/hellofly:latest",
|
|
246
|
+
guest: new MachineGuest({ cpu_kind: "shared", cpus: 1, memory_mb: 256 }),
|
|
247
|
+
}),
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
export { app, web };
|
|
251
|
+
`,
|
|
252
|
+
},
|
|
253
|
+
};
|
|
254
|
+
},
|
|
255
|
+
completionProvider(ctx) {
|
|
256
|
+
return completions(ctx);
|
|
257
|
+
},
|
|
258
|
+
hoverProvider(ctx) {
|
|
259
|
+
return hover(ctx);
|
|
260
|
+
},
|
|
261
|
+
async docs(options) {
|
|
262
|
+
const { generateDocs } = await import("./codegen/docs.js");
|
|
263
|
+
return generateDocs(options);
|
|
264
|
+
},
|
|
265
|
+
// State: the read-back seam for plan + drift (#767). Lists live Fly resources
|
|
266
|
+
// over flaps, keyed by chant entity name, with an ownership verdict core's
|
|
267
|
+
// change set reads. Endpoint + auth reuse the applier (FLY_FLAPS_BASE_URL /
|
|
268
|
+
// FLY_API_TOKEN), so it reads the same target flyApply writes.
|
|
269
|
+
async describeResources(options) {
|
|
270
|
+
const { describeResources } = await import("./describe-resources.js");
|
|
271
|
+
return describeResources(options);
|
|
272
|
+
},
|
|
273
|
+
// Env teardown (#1222): enumeration + execution over the machine marker
|
|
274
|
+
// (`managed-by` + `chant-stack` + `chant-env`) and the app boundary (#743).
|
|
275
|
+
// Machines are destroyed first, whole apps last — an app hosting any machine
|
|
276
|
+
// outside the requested identity is never deleted whole. See ./teardown.ts.
|
|
277
|
+
async teardownOwned(options) {
|
|
278
|
+
const { teardownOwned } = await import("./teardown.js");
|
|
279
|
+
return teardownOwned(options);
|
|
280
|
+
},
|
|
281
|
+
async executeTeardown(options) {
|
|
282
|
+
const { executeTeardown } = await import("./teardown.js");
|
|
283
|
+
return executeTeardown(options);
|
|
284
|
+
},
|
|
285
|
+
// Live export (cloud → code): read live flaps state and regenerate App/Machine
|
|
286
|
+
// TypeScript. Strips server-written fields to the authored shape; honors the
|
|
287
|
+
// `owned` filter (machine marker; app-boundary inference for the marker-less
|
|
288
|
+
// app, logged per the seam contract). Reuses the applier's endpoint/auth and
|
|
289
|
+
// listMachines so it reads the same target flyApply writes.
|
|
290
|
+
async exportResources(options) {
|
|
291
|
+
const { exportResources } = await import("./export-resources.js");
|
|
292
|
+
return exportResources(options);
|
|
293
|
+
},
|
|
294
|
+
// Live graph edges (#804): how observed Fly resources reference each other, so
|
|
295
|
+
// `chant graph --live` reconstructs the topology (App boundary + mount edges).
|
|
296
|
+
// No `enrichLiveAttrs` needed — describeResources already returns rich attrs.
|
|
297
|
+
referenceCatalog: flyReferenceCatalog,
|
|
298
|
+
};
|
package/dist/pseudo.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { PseudoParameter, createPseudoParameters } from "@intentius/chant/pseudo-parameter";
|
|
2
|
+
export { PseudoParameter };
|
|
3
|
+
/**
|
|
4
|
+
* Fly pseudo-parameters — environment-resolved values a project references
|
|
5
|
+
* without hard-coding. Each serializes to a `{ Ref: "Fly::..." }` marker that
|
|
6
|
+
* the serializer resolves from an environment variable at build time (see
|
|
7
|
+
* PSEUDO_ENV_MAP in ./serializer.ts), mirroring gcp's `GCP.Region` /
|
|
8
|
+
* `GCP.ProjectId`.
|
|
9
|
+
*
|
|
10
|
+
* - Region → the target Fly region (FLY_REGION), e.g. `iad`.
|
|
11
|
+
* - OrgSlug → the owning org slug (FLY_ORG / FLY_ORG_SLUG).
|
|
12
|
+
* - AppName → the target app name (FLY_APP_NAME).
|
|
13
|
+
*/
|
|
14
|
+
export const { Region, OrgSlug, AppName } = createPseudoParameters({
|
|
15
|
+
Region: "Fly::Region",
|
|
16
|
+
OrgSlug: "Fly::OrgSlug",
|
|
17
|
+
AppName: "Fly::AppName",
|
|
18
|
+
});
|
|
19
|
+
/**
|
|
20
|
+
* Fly namespace containing all pseudo-parameters.
|
|
21
|
+
*/
|
|
22
|
+
export const Fly = {
|
|
23
|
+
Region,
|
|
24
|
+
OrgSlug,
|
|
25
|
+
AppName,
|
|
26
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fly reference catalog (#804) — how observed Fly resources reference each other,
|
|
3
|
+
* so `chant graph --live` reconstructs the topology and draws the App as a
|
|
4
|
+
* boundary box (the reference resolver, chant#778; containment, chant#779).
|
|
5
|
+
*
|
|
6
|
+
* Fly's relationships are app-centric and simple. `describeResources`
|
|
7
|
+
* (./describe-resources.ts) already returns the rich per-resource shape — each
|
|
8
|
+
* app-scoped resource carries its owning `app`, and a machine carries its full
|
|
9
|
+
* `config` (with `mounts`) — so, unlike AWS (chant#784), Fly needs **no**
|
|
10
|
+
* `enrichLiveAttrs`: the references are already in the observed attributes.
|
|
11
|
+
*
|
|
12
|
+
* Keyed to the `describeResources` attribute shape:
|
|
13
|
+
* - App → `{ app }` (physicalId = app name)
|
|
14
|
+
* - Machine → `{ app, config }` (config.mounts[].volume references a Volume)
|
|
15
|
+
* - Volume → `{ app, volumeName }`
|
|
16
|
+
* - IPAddress → `{ app, ... }`
|
|
17
|
+
* - Certificate→ `{ app, ... }`
|
|
18
|
+
*/
|
|
19
|
+
export const flyReferenceCatalog = {
|
|
20
|
+
identities: [
|
|
21
|
+
// The App is identified by its name (also its physicalId), which every
|
|
22
|
+
// app-scoped resource carries in `app`.
|
|
23
|
+
{ kind: "Fly::Machines::App", ids: ["app"] },
|
|
24
|
+
// A Volume is referenced by name; its id is already indexed via physicalId,
|
|
25
|
+
// so a mount that names either resolves.
|
|
26
|
+
{ kind: "Fly::Machines::Volume", ids: ["volumeName"] },
|
|
27
|
+
],
|
|
28
|
+
refs: [
|
|
29
|
+
// Containment: everything app-scoped lives inside its App → a boundary box.
|
|
30
|
+
{ from: "Fly::Machines::Machine", path: "app", targetKind: "Fly::Machines::App", relation: "containment", label: "in app" },
|
|
31
|
+
{ from: "Fly::Machines::Volume", path: "app", targetKind: "Fly::Machines::App", relation: "containment", label: "in app" },
|
|
32
|
+
{ from: "Fly::Machines::IPAddress", path: "app", targetKind: "Fly::Machines::App", relation: "containment", label: "in app" },
|
|
33
|
+
{ from: "Fly::Machines::Certificate", path: "app", targetKind: "Fly::Machines::App", relation: "containment", label: "in app" },
|
|
34
|
+
// Reference edge: a machine mount → the Volume it mounts.
|
|
35
|
+
{ from: "Fly::Machines::Machine", path: "config.mounts[].volume", targetKind: "Fly::Machines::Volume", relation: "reference", label: "mounts" },
|
|
36
|
+
],
|
|
37
|
+
};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The release a Fly Machine serves, as its metadata records it (#2736, ws-056).
|
|
3
|
+
*
|
|
4
|
+
* Pure: the keys, a reader, and a writer over a Machine config. The Machines
|
|
5
|
+
* activities (./op/activities/machine-release.ts) stamp these on a release,
|
|
6
|
+
* `describeResources` (./describe-resources.ts) reads them back, and
|
|
7
|
+
* `chant components status --live` compares the digest with the release
|
|
8
|
+
* ledger.
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* The Machine metadata keys that say which release it serves. Fly metadata is
|
|
12
|
+
* a string map, so each field is its own key.
|
|
13
|
+
*
|
|
14
|
+
* - `digest`: the release's digest, the release ledger's join key.
|
|
15
|
+
* - `gitSha`: the commit the release was built from.
|
|
16
|
+
* - `release`: an optional human label (a tag, a release id).
|
|
17
|
+
* - `previousDigest`: the release this one replaced when it first shipped, so
|
|
18
|
+
* a rollback knows where to go back to with no history of its own.
|
|
19
|
+
*/
|
|
20
|
+
export const RELEASE_METADATA_KEYS = {
|
|
21
|
+
digest: "chant-release-digest",
|
|
22
|
+
gitSha: "chant-release-git-sha",
|
|
23
|
+
release: "chant-release",
|
|
24
|
+
previousDigest: "chant-release-previous-digest",
|
|
25
|
+
};
|
|
26
|
+
/** The release a Machine's metadata names, or undefined when it names none. Pure. */
|
|
27
|
+
export function readMachineRelease(metadata) {
|
|
28
|
+
const digest = metadata?.[RELEASE_METADATA_KEYS.digest];
|
|
29
|
+
if (!digest)
|
|
30
|
+
return undefined;
|
|
31
|
+
const gitSha = metadata?.[RELEASE_METADATA_KEYS.gitSha];
|
|
32
|
+
const release = metadata?.[RELEASE_METADATA_KEYS.release];
|
|
33
|
+
const previousDigest = metadata?.[RELEASE_METADATA_KEYS.previousDigest];
|
|
34
|
+
return {
|
|
35
|
+
digest,
|
|
36
|
+
...(gitSha ? { gitSha } : {}),
|
|
37
|
+
...(release ? { release } : {}),
|
|
38
|
+
...(previousDigest ? { previousDigest } : {}),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* A Machine config serving `release`: a copy of `config` with the release's
|
|
43
|
+
* metadata keys set (and any it does not carry removed). Pure.
|
|
44
|
+
*/
|
|
45
|
+
export function withReleaseMetadata(config, release) {
|
|
46
|
+
const out = structuredClone(config ?? {});
|
|
47
|
+
const metadata = { ...(out.metadata ?? {}) };
|
|
48
|
+
for (const key of Object.values(RELEASE_METADATA_KEYS))
|
|
49
|
+
delete metadata[key];
|
|
50
|
+
metadata[RELEASE_METADATA_KEYS.digest] = release.digest;
|
|
51
|
+
if (release.gitSha)
|
|
52
|
+
metadata[RELEASE_METADATA_KEYS.gitSha] = release.gitSha;
|
|
53
|
+
if (release.release)
|
|
54
|
+
metadata[RELEASE_METADATA_KEYS.release] = release.release;
|
|
55
|
+
if (release.previousDigest)
|
|
56
|
+
metadata[RELEASE_METADATA_KEYS.previousDigest] = release.previousDigest;
|
|
57
|
+
out.metadata = metadata;
|
|
58
|
+
return out;
|
|
59
|
+
}
|