@intentius/chant-lexicon-fountain 0.90.0 → 0.91.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,11 +1,15 @@
1
1
  /**
2
2
  * Box — a persistent machine that serves an app repo and its tools (#2705).
3
3
  *
4
- * A box is an `Environment` whose repositories hold the app repo and whose
5
- * `setup_script` provisions the machine (for an arugula studio box, the
6
- * studio's `box/provision-template.sh`), an `Agent` on a persistent sandbox
7
- * provisioned from it, and optionally a `Vault` for the secrets every box of
8
- * this kind shares.
4
+ * A box is an `Environment` whose `setup_script` provisions the machine (for
5
+ * an arugula studio box, the studio's `box/provision-template.sh`), an
6
+ * `Agent` on a persistent sandbox provisioned from it, and optionally a
7
+ * `Vault` for the secrets every box of this kind shares. `repo` is optional:
8
+ * when given, the Environment's `repositories` clones it in before setup
9
+ * runs; when omitted (#2759 — a blank-slate app, such as the one hud's
10
+ * `init` makes, has no git remote yet), the Environment holds no
11
+ * `repositories` at all and `setup_script` still runs, against whatever
12
+ * working tree is already on the sandbox (a self-hosted `runner`'s own).
9
13
  *
10
14
  * ```ts
11
15
  * export const { environment, agent, vault } = Box({
@@ -14,7 +18,7 @@
14
18
  * setupScript: readFileSync("box/provision-template.sh", "utf8"),
15
19
  * permissionPolicy: { default: "auto_allow" },
16
20
  * allowedHosts: ["registry.npmjs.org", "github.com"],
17
- * vault: { secrets: [{ key: "STUDIO_SECRET", value: process.env.STUDIO_SECRET! }] },
21
+ * vault: { secrets: [{ key: "STUDIO_SECRET", value: "${STUDIO_SECRET}" }] },
18
22
  * });
19
23
  * ```
20
24
  *
@@ -22,18 +26,34 @@
22
26
  * whose every turn is a chant command line, bound to a teammate with a
23
27
  * standing thread and schedules. A box is where people and their agents work
24
28
  * on an app: a conversational runtime (`claude` by default), the app repo
25
- * cloned in, the provisioning script run as setup, and one served port. It
26
- * declares no teammate and no schedule, and nothing here starts a
27
- * conversation. Starting and reaping a box stays a runtime act, as it is in
28
- * hud's box runtime.
29
+ * cloned in (when there is one), the provisioning script run as setup, and
30
+ * one served port. It declares no teammate and no schedule, and nothing here
31
+ * starts a conversation. Starting and reaping a box stays a runtime act, as
32
+ * it is in hud's box runtime.
29
33
  *
30
34
  * The defaults are the closed ones, as `ConciergeStack`'s are, and loosening
31
35
  * one is a visible parameter: networking is `limited` with an empty allowlist
32
36
  * until `allowedHosts` names hosts or `unrestrictedNetworking: true` opens it
33
37
  * (which FTN011 then warns about); `allowed_vault_ids` holds the box's own
34
- * vault, or nothing, until `allowedVaults` widens it. `permission_policy` has
35
- * no default at all, because fountain's unset policy is `auto_allow` and that
36
- * should be a choice someone wrote down.
38
+ * vault, or nothing, until `allowedVaults` widens it; `sandbox_provider` is
39
+ * unset (the instance default) until `sandboxProvider` names one of the
40
+ * pinned spec's `sprites`, `e2b`, `daytona` or `runner`. `permission_policy`
41
+ * has no default at all, because fountain's unset policy is `auto_allow` and
42
+ * that should be a choice someone wrote down.
43
+ *
44
+ * ## Credentials
45
+ *
46
+ * A box holds no credential (#2726). What it reaches outside itself
47
+ * (inference, Fountain's own API) goes through a broker, such as a studio's
48
+ * lobby, that holds the key and enforces a scope. Those capabilities are
49
+ * declared in the workspace declaration's `box` block on the member that
50
+ * holds this composite, not here: `chant workspace check` and the read
51
+ * contract read the declaration without running this code. Here, a vault
52
+ * secret's value and an `envVars` value are references (`${VAR}`, `op://`,
53
+ * `bws://`, `infisical://`) that resolve at apply, never a literal, and never
54
+ * `process.env.X`, which writes the secret into the built manifest. `chant
55
+ * workspace check` fails a box member whose files hold one
56
+ * (`box-credential-declared`).
37
57
  *
38
58
  * ## The port
39
59
  *
@@ -41,10 +61,31 @@
41
61
  * pinned spec, v0.21.0). The port is recorded in the metadata of both, under
42
62
  * `box-port`, and returned as `port`, so whatever builds the box's URL reads
43
63
  * it from the declaration or from fountain's own record without a guess.
64
+ *
65
+ * ## Allowed vaults in the read contract
66
+ *
67
+ * `sandbox_provider` is a plain `Agent` field, so `chant workspace graph
68
+ * --json` (and `chant graph`) already carry it on the agent's node like any
69
+ * other declared attribute. `allowed_vault_ids` is not as plain to read:
70
+ * fountain's own convention (and this composite's, per `allowedVaults`
71
+ * above) is that an *unset* list means "any vault the tenant owns", so a
72
+ * graph reader can't tell "no vaults declared" from "any vault" just by the
73
+ * key's absence. #2759 — a runtime that creates a vault per box (hud's box
74
+ * runtime) needs to check that the declaration permits it — so the resolved
75
+ * list, or the literal string `"any"`, is also recorded in the agent's
76
+ * metadata under `box-allowed-vault-ids` (`BOX_ALLOWED_VAULTS_METADATA_KEY`).
77
+ * It is read-only bookkeeping, alongside `box-port`: the manifest's real
78
+ * `allowed_vault_ids` field is what fountain applies.
44
79
  */
45
80
  import { Agent, Environment, Vault } from "../generated/index.js";
46
81
  /** The metadata key a box's served port is recorded under, on its Environment and Agent. */
47
82
  export declare const BOX_PORT_METADATA_KEY = "box-port";
83
+ /** The metadata key a box agent's resolved allowed vaults (or `"any"`) are recorded under, for the read contract. */
84
+ export declare const BOX_ALLOWED_VAULTS_METADATA_KEY = "box-allowed-vault-ids";
85
+ /** `Agent.sandbox_provider`'s values at the pinned spec. */
86
+ export declare const BOX_SANDBOX_PROVIDERS: readonly ["sprites", "e2b", "daytona", "runner"];
87
+ /** A sandbox backend, per the pinned spec's `sandbox_provider` enum. */
88
+ export type BoxSandboxProvider = (typeof BOX_SANDBOX_PROVIDERS)[number];
48
89
  /** The port a box serves when none is given: the door's. */
49
90
  export declare const BOX_DEFAULT_PORT = 8080;
50
91
  /** Where the app repo is cloned when no `mountPath` is given. */
@@ -67,7 +108,7 @@ export interface BoxVaultOpts {
67
108
  /** Vault name. Default `<name>-secrets`. */
68
109
  name?: string;
69
110
  description?: string;
70
- /** Written at apply. A reference that resolves at build, never a literal (FTN001). */
111
+ /** Written at apply. A reference such as `${VAR}` or `op://...` that resolves at apply, never a literal (FTN001, and `box-credential-declared` in `chant workspace check`). */
71
112
  secrets?: {
72
113
  key: string;
73
114
  value: string;
@@ -76,8 +117,8 @@ export interface BoxVaultOpts {
76
117
  export interface BoxOpts {
77
118
  /** The Agent's name. The Environment is `<name>-env`. */
78
119
  name: string;
79
- /** The app repo the box serves, cloned into the sandbox before setup runs. */
80
- repo: BoxRepositoryOpts;
120
+ /** The app repo the box serves, cloned into the sandbox before setup runs. Omit for a blank-slate app with no repo yet: the Environment declares no `repositories`, and `setupScript` runs against whatever working tree the sandbox already has. */
121
+ repo?: BoxRepositoryOpts;
81
122
  /** The provisioning script's text, run as the Environment's `setup_script`. */
82
123
  setupScript: string;
83
124
  /** Setup exec timeout, 1 to 900 seconds (FTN024). fountain's default is 120. */
@@ -86,6 +127,8 @@ export interface BoxOpts {
86
127
  runtime?: "claude" | "codex" | "gemini" | "opencode";
87
128
  /** Canonical provider/model_id. Omitted, fountain's default for the runtime. */
88
129
  model?: string;
130
+ /** Sandbox backend override. Omitted, the instance default (`SANDBOX_PROVIDER`). A plain `Agent.sandbox_provider` field, so it is already visible on the graph node without extra bookkeeping (unlike `allowedVaults`; see "Allowed vaults in the read contract" above). */
131
+ sandboxProvider?: BoxSandboxProvider;
89
132
  /** Per-tool permission policy. Required: fountain's unset policy is `auto_allow`. */
90
133
  permissionPolicy: BoxPermissionPolicy;
91
134
  /** Environment packages, passed through. */
@@ -1 +1 @@
1
- {"version":3,"file":"box.d.ts","sourceRoot":"","sources":["../../src/composites/box.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"box.d.ts","sourceRoot":"","sources":["../../src/composites/box.ts"],"names":[],"mappings":"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"}
@@ -44,7 +44,43 @@
44
44
  * the server does not keep. And a webhook url FTN022 would reject is refused
45
45
  * here rather than at synth, because a composite that constructed it would be
46
46
  * handing the author a lint error about a resource they never typed.
47
+ *
48
+ * ## One steward whatever the preset (#2731)
49
+ *
50
+ * The composite also returns `declaration`, the steward as core reads it
51
+ * (`declareSteward` in `@intentius/chant/op`): its name, its Ops and its
52
+ * `form`. A box whose preset has no Fountain in some environment (a docker
53
+ * compose box, a Sprites box) sets `form` so that environment is `local`, and
54
+ * there `chant operator --steward <name>` runs the same Ops on the same crons,
55
+ * one at a time, each under its operator lease and the whole process under the
56
+ * steward's own. `chant operator --steward` refuses an environment whose form
57
+ * is `fountain`, because this Agent and Teammate are the steward there. Export
58
+ * `declaration` from an `*.op.ts` file so the operator and `chant workspace
59
+ * status` find it.
60
+ *
61
+ * ## Credentials (#2726)
62
+ *
63
+ * A box's steward holds no credential. Name the box capabilities its Ops use
64
+ * in `capabilities` (the names the member's `box` block in the workspace
65
+ * declaration gives them) and leave `vault` out: the broker holds the key and
66
+ * enforces the scope. `vault` together with `capabilities` is refused.
67
+ * `chant workspace check` fails a box member whose files hold a literal secret
68
+ * (`box-credential-declared`), and `chant workspace status --json` lists each
69
+ * capability the steward names with the broker the box block gives it.
70
+ *
71
+ * ## The coding agent beside it
72
+ *
73
+ * A box also has a coding agent editing the app. They share the checkout by
74
+ * writing different things. The steward's writes are chant writes: run,
75
+ * converge and gate records on the `chant/lifecycle` branch and lease refs
76
+ * under `refs/chant/lease/`, made with git plumbing that never reads or writes
77
+ * the index or the working tree. The app's files, the index and commits on the
78
+ * working branch are the coding agent's. An Op a steward runs does not edit the
79
+ * app. A change to the checkout that an Op has to make (applying a build, a
80
+ * workspace upgrade) belongs under the work item's lease (ws-055), on a branch
81
+ * of its own, never in the coding agent's working tree.
47
82
  */
83
+ import { type StewardDeclaration, type StewardFormSpec } from "@intentius/chant/op";
48
84
  import type { OpConfig, OpResource } from "@intentius/chant/op";
49
85
  import { Agent, Environment, Schedule, Teammate, Vault, Webhook } from "../generated/index.js";
50
86
  /**
@@ -71,8 +107,18 @@ export interface StewardOpts {
71
107
  name: string;
72
108
  /** The environment its computer is provisioned from: repo, chant, tooling. */
73
109
  environment: InstanceType<typeof Environment>;
74
- /** Secrets layered on top. Omit under the egress broker, which holds them. */
110
+ /**
111
+ * Secrets layered on top, for a steward that isn't behind a broker. Omit it
112
+ * under a broker, which holds them, and name `capabilities` instead.
113
+ */
75
114
  vault?: InstanceType<typeof Vault>;
115
+ /**
116
+ * The box capabilities this steward's Ops reach through the box's broker
117
+ * (#2726), by the names the member's `box` block declares, such as
118
+ * `fountain` or `inference`. Refused together with `vault`: a brokered
119
+ * steward holds no credential.
120
+ */
121
+ capabilities?: string[];
76
122
  /**
77
123
  * The ops this steward runs. An op with a `schedule` gets a `Schedule`; one
78
124
  * without is still listed, so `chant run <op> --on fountain` knows which
@@ -82,6 +128,16 @@ export interface StewardOpts {
82
128
  webhook?: StewardWebhookOpts;
83
129
  /** Extra metadata merged over the `managed-by` marker on the Agent. */
84
130
  metadata?: Record<string, unknown>;
131
+ /**
132
+ * Where this steward runs (#2731). Default `fountain`: this composite's
133
+ * Agent and Teammate are the steward. A box whose preset has no Fountain in
134
+ * some environment names it here, e.g. `{ default: "fountain", environments:
135
+ * { minimal: "local" } }`, and `chant operator --steward <name> --env minimal`
136
+ * runs the same Ops there. The Fountain resources are declared either way; a
137
+ * form that is `local` everywhere is refused, since it needs none of them
138
+ * (use core's `declareSteward` for that).
139
+ */
140
+ form?: StewardFormSpec;
85
141
  }
86
142
  export interface StewardResources {
87
143
  agent: InstanceType<typeof Agent>;
@@ -89,6 +145,12 @@ export interface StewardResources {
89
145
  /** One per op that carries a `schedule`, in `ops` order. */
90
146
  schedules: InstanceType<typeof Schedule>[];
91
147
  webhook?: InstanceType<typeof Webhook>;
148
+ /**
149
+ * The steward as core reads it (#2731): name, Ops and form. Export it from
150
+ * an `*.op.ts` file so `chant operator --steward` and `chant workspace
151
+ * status` find it.
152
+ */
153
+ declaration: StewardDeclaration;
92
154
  }
93
155
  /** The command an `acp` agent speaks the protocol over (#2125). */
94
156
  export declare const STEWARD_RUNTIME_COMMAND = "chant acp";
@@ -1 +1 @@
1
- {"version":3,"file":"steward.d.ts","sourceRoot":"","sources":["../../src/composites/steward.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAChE,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,QAAQ,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAK5F;;;;;;;;;GASG;AACH,MAAM,MAAM,SAAS,GAAG,QAAQ,GAAG,YAAY,CAAC,OAAO,UAAU,CAAC,CAAC;AAQnE,kFAAkF;AAClF,MAAM,WAAW,kBAAkB;IACjC,8EAA8E;IAC9E,GAAG,EAAE,MAAM,CAAC;IACZ,mEAAmE;IACnE,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,WAAW;IAC1B,4DAA4D;IAC5D,IAAI,EAAE,MAAM,CAAC;IACb,8EAA8E;IAC9E,WAAW,EAAE,YAAY,CAAC,OAAO,WAAW,CAAC,CAAC;IAC9C,8EAA8E;IAC9E,KAAK,CAAC,EAAE,YAAY,CAAC,OAAO,KAAK,CAAC,CAAC;IACnC;;;;OAIG;IACH,GAAG,EAAE,SAAS,EAAE,CAAC;IACjB,OAAO,CAAC,EAAE,kBAAkB,CAAC;IAC7B,uEAAuE;IACvE,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC;AAED,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,YAAY,CAAC,OAAO,KAAK,CAAC,CAAC;IAClC,QAAQ,EAAE,YAAY,CAAC,OAAO,QAAQ,CAAC,CAAC;IACxC,4DAA4D;IAC5D,SAAS,EAAE,YAAY,CAAC,OAAO,QAAQ,CAAC,EAAE,CAAC;IAC3C,OAAO,CAAC,EAAE,YAAY,CAAC,OAAO,OAAO,CAAC,CAAC;CACxC;AAED,mEAAmE;AACnE,eAAO,MAAM,uBAAuB,cAAc,CAAC;AAiBnD,iFAAiF;AACjF,wBAAgB,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAE3D;AAED,iFAAiF;AACjF,wBAAgB,uBAAuB,IAAI,IAAI,CAG9C;AA8BD,wBAAgB,OAAO,CAAC,IAAI,EAAE,WAAW,GAAG,gBAAgB,CAsF3D"}
1
+ {"version":3,"file":"steward.d.ts","sourceRoot":"","sources":["../../src/composites/steward.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiFG;AAEH,OAAO,EAAkB,KAAK,kBAAkB,EAAE,KAAK,eAAe,EAAE,MAAM,qBAAqB,CAAC;AACpG,OAAO,KAAK,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAChE,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,QAAQ,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAK5F;;;;;;;;;GASG;AACH,MAAM,MAAM,SAAS,GAAG,QAAQ,GAAG,YAAY,CAAC,OAAO,UAAU,CAAC,CAAC;AAQnE,kFAAkF;AAClF,MAAM,WAAW,kBAAkB;IACjC,8EAA8E;IAC9E,GAAG,EAAE,MAAM,CAAC;IACZ,mEAAmE;IACnE,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,WAAW;IAC1B,4DAA4D;IAC5D,IAAI,EAAE,MAAM,CAAC;IACb,8EAA8E;IAC9E,WAAW,EAAE,YAAY,CAAC,OAAO,WAAW,CAAC,CAAC;IAC9C;;;OAGG;IACH,KAAK,CAAC,EAAE,YAAY,CAAC,OAAO,KAAK,CAAC,CAAC;IACnC;;;;;OAKG;IACH,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB;;;;OAIG;IACH,GAAG,EAAE,SAAS,EAAE,CAAC;IACjB,OAAO,CAAC,EAAE,kBAAkB,CAAC;IAC7B,uEAAuE;IACvE,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC;;;;;;;;OAQG;IACH,IAAI,CAAC,EAAE,eAAe,CAAC;CACxB;AAED,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,YAAY,CAAC,OAAO,KAAK,CAAC,CAAC;IAClC,QAAQ,EAAE,YAAY,CAAC,OAAO,QAAQ,CAAC,CAAC;IACxC,4DAA4D;IAC5D,SAAS,EAAE,YAAY,CAAC,OAAO,QAAQ,CAAC,EAAE,CAAC;IAC3C,OAAO,CAAC,EAAE,YAAY,CAAC,OAAO,OAAO,CAAC,CAAC;IACvC;;;;OAIG;IACH,WAAW,EAAE,kBAAkB,CAAC;CACjC;AAED,mEAAmE;AACnE,eAAO,MAAM,uBAAuB,cAAc,CAAC;AAiBnD,iFAAiF;AACjF,wBAAgB,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAE3D;AAED,iFAAiF;AACjF,wBAAgB,uBAAuB,IAAI,IAAI,CAG9C;AA8BD,wBAAgB,OAAO,CAAC,IAAI,EAAE,WAAW,GAAG,gBAAgB,CAuG3D"}
package/dist/index.d.ts CHANGED
@@ -14,8 +14,8 @@ export { ConciergeStack } from "./composites/concierge-stack.js";
14
14
  export type { ConciergeStackOpts, ConciergeStackResources } from "./composites/concierge-stack.js";
15
15
  export { Steward, stewardForOp, STEWARD_RUNTIME_COMMAND } from "./composites/steward.js";
16
16
  export type { StewardOp, StewardOpts, StewardResources, StewardWebhookOpts } from "./composites/steward.js";
17
- export { Box, BOX_PORT_METADATA_KEY, BOX_DEFAULT_PORT, BOX_DEFAULT_MOUNT_PATH } from "./composites/box.js";
18
- export type { BoxOpts, BoxResources, BoxRepositoryOpts, BoxVaultOpts, BoxPermissionPolicy } from "./composites/box.js";
17
+ export { Box, BOX_PORT_METADATA_KEY, BOX_ALLOWED_VAULTS_METADATA_KEY, BOX_DEFAULT_PORT, BOX_DEFAULT_MOUNT_PATH, BOX_SANDBOX_PROVIDERS, } from "./composites/box.js";
18
+ export type { BoxOpts, BoxResources, BoxRepositoryOpts, BoxVaultOpts, BoxPermissionPolicy, BoxSandboxProvider } from "./composites/box.js";
19
19
  export { acpCommandGroup } from "./acp/index.js";
20
20
  export { AcpServer } from "./acp/server.js";
21
21
  export type { AcpServerOptions } from "./acp/server.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAG1C,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAIlD,OAAO,EAAE,4BAA4B,EAAE,MAAM,gBAAgB,CAAC;AAC9D,YAAY,EAAE,0BAA0B,EAAE,MAAM,gBAAgB,CAAC;AACjE,OAAO,EACL,8BAA8B,EAC9B,sBAAsB,EACtB,iBAAiB,GAClB,MAAM,sBAAsB,CAAC;AAG9B,cAAc,mBAAmB,CAAC;AAIlC,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,yBAAyB,EAAE,MAAM,iBAAiB,CAAC;AACxF,YAAY,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAInH,OAAO,EAAE,oBAAoB,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAChE,YAAY,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAIhE,OAAO,EACL,uBAAuB,EACvB,kBAAkB,EAClB,8BAA8B,GAC/B,MAAM,cAAc,CAAC;AACtB,YAAY,EAAE,wBAAwB,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAG5F,OAAO,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAC9D,YAAY,EAAE,kBAAkB,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AAChG,OAAO,EAAE,OAAO,EAAE,YAAY,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAC;AACtF,YAAY,EAAE,SAAS,EAAE,WAAW,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AACzG,OAAO,EAAE,GAAG,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;AACxG,YAAY,EAAE,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAIpH,OAAO,EAAE,eAAe,EAAE,MAAM,OAAO,CAAC;AACxC,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzC,YAAY,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAC7C,YAAY,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAG1C,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAIlD,OAAO,EAAE,4BAA4B,EAAE,MAAM,gBAAgB,CAAC;AAC9D,YAAY,EAAE,0BAA0B,EAAE,MAAM,gBAAgB,CAAC;AACjE,OAAO,EACL,8BAA8B,EAC9B,sBAAsB,EACtB,iBAAiB,GAClB,MAAM,sBAAsB,CAAC;AAG9B,cAAc,mBAAmB,CAAC;AAIlC,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,yBAAyB,EAAE,MAAM,iBAAiB,CAAC;AACxF,YAAY,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAInH,OAAO,EAAE,oBAAoB,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAChE,YAAY,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAIhE,OAAO,EACL,uBAAuB,EACvB,kBAAkB,EAClB,8BAA8B,GAC/B,MAAM,cAAc,CAAC;AACtB,YAAY,EAAE,wBAAwB,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAG5F,OAAO,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAC9D,YAAY,EAAE,kBAAkB,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AAChG,OAAO,EAAE,OAAO,EAAE,YAAY,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAC;AACtF,YAAY,EAAE,SAAS,EAAE,WAAW,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AACzG,OAAO,EACL,GAAG,EACH,qBAAqB,EACrB,+BAA+B,EAC/B,gBAAgB,EAChB,sBAAsB,EACtB,qBAAqB,GACtB,MAAM,kBAAkB,CAAC;AAC1B,YAAY,EAAE,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,YAAY,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAIxI,OAAO,EAAE,eAAe,EAAE,MAAM,OAAO,CAAC;AACxC,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzC,YAAY,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAC7C,YAAY,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC"}
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "algorithm": "sha256",
3
3
  "artifacts": {
4
- "manifest.json": "574b2bc5f0f9b2c0125e34c21d47fbdc1f154e558f9ff61e585a41b0c2c913da",
4
+ "manifest.json": "f55b301389d73946391200a1788013f3b7f368586b497af230a6813a0acda4e8",
5
5
  "meta.json": "3301437a2a56eaf1a5cc8f6eabb18a40767f916e09701b2d9bf20aba4066738c",
6
6
  "types/index.d.ts": "cf6b634fe0a8e9e96f1d9d07e3df53838def2683dc93dbe3c7fd971c6d3908ae",
7
7
  "rules/ftn001-no-secret-literals.ts": "a417b194cd7039911cca9a0f14c49b042dfa6b1ee48517f7c2e22b16590a8599",
@@ -23,5 +23,5 @@
23
23
  "skills/chant-fountain-ops.md": "4e72f0a0c7477a9e98f85ea45e49ba69e8a699f9c13ba0f0cf1cb20a88151373",
24
24
  "skills/chant-fountain-locked-sandboxes.md": "14def4c68b13c2bd561f75819b188968cec85073a5dd8b59e91708e1af26daae"
25
25
  },
26
- "composite": "21bc34705dabf0dfe5a50d7bb81bf76d54cf4647b5f5790cd66e54de1d4f9a5f"
26
+ "composite": "d961f51d2dbd943d5da0fb56f8c82649c3392c02c62222d68c81069b9c9cdefb"
27
27
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fountain",
3
- "version": "0.90.0",
3
+ "version": "0.91.0",
4
4
  "chantVersion": ">=0.1.0",
5
5
  "namespace": "Fountain",
6
6
  "specVersion": "v0.21.0"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@intentius/chant-lexicon-fountain",
3
- "version": "0.90.0",
3
+ "version": "0.91.0",
4
4
  "type": "module",
5
5
  "description": "Fountain lexicon for chant — sandboxed agent environments, vaults, and agents as typed estate",
6
6
  "license": "Apache-2.0",
@@ -50,7 +50,7 @@
50
50
  "bundle": "tsx src/package-cli.ts"
51
51
  },
52
52
  "peerDependencies": {
53
- "@intentius/chant": "^0.90.0",
53
+ "@intentius/chant": "^0.91.0",
54
54
  "typescript": "^5.9.3",
55
55
  "zod": "^4.3.6"
56
56
  },
@@ -1,11 +1,15 @@
1
1
  /**
2
2
  * Box — a persistent machine that serves an app repo and its tools (#2705).
3
3
  *
4
- * A box is an `Environment` whose repositories hold the app repo and whose
5
- * `setup_script` provisions the machine (for an arugula studio box, the
6
- * studio's `box/provision-template.sh`), an `Agent` on a persistent sandbox
7
- * provisioned from it, and optionally a `Vault` for the secrets every box of
8
- * this kind shares.
4
+ * A box is an `Environment` whose `setup_script` provisions the machine (for
5
+ * an arugula studio box, the studio's `box/provision-template.sh`), an
6
+ * `Agent` on a persistent sandbox provisioned from it, and optionally a
7
+ * `Vault` for the secrets every box of this kind shares. `repo` is optional:
8
+ * when given, the Environment's `repositories` clones it in before setup
9
+ * runs; when omitted (#2759 — a blank-slate app, such as the one hud's
10
+ * `init` makes, has no git remote yet), the Environment holds no
11
+ * `repositories` at all and `setup_script` still runs, against whatever
12
+ * working tree is already on the sandbox (a self-hosted `runner`'s own).
9
13
  *
10
14
  * ```ts
11
15
  * export const { environment, agent, vault } = Box({
@@ -14,7 +18,7 @@
14
18
  * setupScript: readFileSync("box/provision-template.sh", "utf8"),
15
19
  * permissionPolicy: { default: "auto_allow" },
16
20
  * allowedHosts: ["registry.npmjs.org", "github.com"],
17
- * vault: { secrets: [{ key: "STUDIO_SECRET", value: process.env.STUDIO_SECRET! }] },
21
+ * vault: { secrets: [{ key: "STUDIO_SECRET", value: "${STUDIO_SECRET}" }] },
18
22
  * });
19
23
  * ```
20
24
  *
@@ -22,18 +26,34 @@
22
26
  * whose every turn is a chant command line, bound to a teammate with a
23
27
  * standing thread and schedules. A box is where people and their agents work
24
28
  * on an app: a conversational runtime (`claude` by default), the app repo
25
- * cloned in, the provisioning script run as setup, and one served port. It
26
- * declares no teammate and no schedule, and nothing here starts a
27
- * conversation. Starting and reaping a box stays a runtime act, as it is in
28
- * hud's box runtime.
29
+ * cloned in (when there is one), the provisioning script run as setup, and
30
+ * one served port. It declares no teammate and no schedule, and nothing here
31
+ * starts a conversation. Starting and reaping a box stays a runtime act, as
32
+ * it is in hud's box runtime.
29
33
  *
30
34
  * The defaults are the closed ones, as `ConciergeStack`'s are, and loosening
31
35
  * one is a visible parameter: networking is `limited` with an empty allowlist
32
36
  * until `allowedHosts` names hosts or `unrestrictedNetworking: true` opens it
33
37
  * (which FTN011 then warns about); `allowed_vault_ids` holds the box's own
34
- * vault, or nothing, until `allowedVaults` widens it. `permission_policy` has
35
- * no default at all, because fountain's unset policy is `auto_allow` and that
36
- * should be a choice someone wrote down.
38
+ * vault, or nothing, until `allowedVaults` widens it; `sandbox_provider` is
39
+ * unset (the instance default) until `sandboxProvider` names one of the
40
+ * pinned spec's `sprites`, `e2b`, `daytona` or `runner`. `permission_policy`
41
+ * has no default at all, because fountain's unset policy is `auto_allow` and
42
+ * that should be a choice someone wrote down.
43
+ *
44
+ * ## Credentials
45
+ *
46
+ * A box holds no credential (#2726). What it reaches outside itself
47
+ * (inference, Fountain's own API) goes through a broker, such as a studio's
48
+ * lobby, that holds the key and enforces a scope. Those capabilities are
49
+ * declared in the workspace declaration's `box` block on the member that
50
+ * holds this composite, not here: `chant workspace check` and the read
51
+ * contract read the declaration without running this code. Here, a vault
52
+ * secret's value and an `envVars` value are references (`${VAR}`, `op://`,
53
+ * `bws://`, `infisical://`) that resolve at apply, never a literal, and never
54
+ * `process.env.X`, which writes the secret into the built manifest. `chant
55
+ * workspace check` fails a box member whose files hold one
56
+ * (`box-credential-declared`).
37
57
  *
38
58
  * ## The port
39
59
  *
@@ -41,6 +61,21 @@
41
61
  * pinned spec, v0.21.0). The port is recorded in the metadata of both, under
42
62
  * `box-port`, and returned as `port`, so whatever builds the box's URL reads
43
63
  * it from the declaration or from fountain's own record without a guess.
64
+ *
65
+ * ## Allowed vaults in the read contract
66
+ *
67
+ * `sandbox_provider` is a plain `Agent` field, so `chant workspace graph
68
+ * --json` (and `chant graph`) already carry it on the agent's node like any
69
+ * other declared attribute. `allowed_vault_ids` is not as plain to read:
70
+ * fountain's own convention (and this composite's, per `allowedVaults`
71
+ * above) is that an *unset* list means "any vault the tenant owns", so a
72
+ * graph reader can't tell "no vaults declared" from "any vault" just by the
73
+ * key's absence. #2759 — a runtime that creates a vault per box (hud's box
74
+ * runtime) needs to check that the declaration permits it — so the resolved
75
+ * list, or the literal string `"any"`, is also recorded in the agent's
76
+ * metadata under `box-allowed-vault-ids` (`BOX_ALLOWED_VAULTS_METADATA_KEY`).
77
+ * It is read-only bookkeeping, alongside `box-port`: the manifest's real
78
+ * `allowed_vault_ids` field is what fountain applies.
44
79
  */
45
80
 
46
81
  import { Agent, Environment, Vault } from "../generated/index";
@@ -48,6 +83,15 @@ import { Agent, Environment, Vault } from "../generated/index";
48
83
  /** The metadata key a box's served port is recorded under, on its Environment and Agent. */
49
84
  export const BOX_PORT_METADATA_KEY = "box-port";
50
85
 
86
+ /** The metadata key a box agent's resolved allowed vaults (or `"any"`) are recorded under, for the read contract. */
87
+ export const BOX_ALLOWED_VAULTS_METADATA_KEY = "box-allowed-vault-ids";
88
+
89
+ /** `Agent.sandbox_provider`'s values at the pinned spec. */
90
+ export const BOX_SANDBOX_PROVIDERS = ["sprites", "e2b", "daytona", "runner"] as const;
91
+
92
+ /** A sandbox backend, per the pinned spec's `sandbox_provider` enum. */
93
+ export type BoxSandboxProvider = (typeof BOX_SANDBOX_PROVIDERS)[number];
94
+
51
95
  /** The port a box serves when none is given: the door's. */
52
96
  export const BOX_DEFAULT_PORT = 8080;
53
97
 
@@ -74,15 +118,15 @@ export interface BoxVaultOpts {
74
118
  /** Vault name. Default `<name>-secrets`. */
75
119
  name?: string;
76
120
  description?: string;
77
- /** Written at apply. A reference that resolves at build, never a literal (FTN001). */
121
+ /** Written at apply. A reference such as `${VAR}` or `op://...` that resolves at apply, never a literal (FTN001, and `box-credential-declared` in `chant workspace check`). */
78
122
  secrets?: { key: string; value: string }[];
79
123
  }
80
124
 
81
125
  export interface BoxOpts {
82
126
  /** The Agent's name. The Environment is `<name>-env`. */
83
127
  name: string;
84
- /** The app repo the box serves, cloned into the sandbox before setup runs. */
85
- repo: BoxRepositoryOpts;
128
+ /** The app repo the box serves, cloned into the sandbox before setup runs. Omit for a blank-slate app with no repo yet: the Environment declares no `repositories`, and `setupScript` runs against whatever working tree the sandbox already has. */
129
+ repo?: BoxRepositoryOpts;
86
130
  /** The provisioning script's text, run as the Environment's `setup_script`. */
87
131
  setupScript: string;
88
132
  /** Setup exec timeout, 1 to 900 seconds (FTN024). fountain's default is 120. */
@@ -91,6 +135,8 @@ export interface BoxOpts {
91
135
  runtime?: "claude" | "codex" | "gemini" | "opencode";
92
136
  /** Canonical provider/model_id. Omitted, fountain's default for the runtime. */
93
137
  model?: string;
138
+ /** Sandbox backend override. Omitted, the instance default (`SANDBOX_PROVIDER`). A plain `Agent.sandbox_provider` field, so it is already visible on the graph node without extra bookkeeping (unlike `allowedVaults`; see "Allowed vaults in the read contract" above). */
139
+ sandboxProvider?: BoxSandboxProvider;
94
140
  /** Per-tool permission policy. Required: fountain's unset policy is `auto_allow`. */
95
141
  permissionPolicy: BoxPermissionPolicy;
96
142
  /** Environment packages, passed through. */
@@ -144,16 +190,18 @@ export function Box(opts: BoxOpts): BoxResources {
144
190
  const owned = { "managed-by": "chant", ...(opts.metadata ?? {}) };
145
191
  const metadata = { ...owned, [BOX_PORT_METADATA_KEY]: port };
146
192
 
147
- const repository = {
148
- url: opts.repo.url,
149
- mount_path: opts.repo.mountPath ?? BOX_DEFAULT_MOUNT_PATH,
150
- ...(opts.repo.ref !== undefined ? { ref: opts.repo.ref } : {}),
151
- ...(opts.repo.secretKey !== undefined ? { secret_key: opts.repo.secretKey } : {}),
152
- };
193
+ const repository = opts.repo
194
+ ? {
195
+ url: opts.repo.url,
196
+ mount_path: opts.repo.mountPath ?? BOX_DEFAULT_MOUNT_PATH,
197
+ ...(opts.repo.ref !== undefined ? { ref: opts.repo.ref } : {}),
198
+ ...(opts.repo.secretKey !== undefined ? { secret_key: opts.repo.secretKey } : {}),
199
+ }
200
+ : undefined;
153
201
 
154
202
  const environment = new Environment({
155
203
  name: `${opts.name}-env`,
156
- repositories: [repository],
204
+ ...(repository ? { repositories: [repository] } : {}),
157
205
  setup_script: opts.setupScript,
158
206
  ...(opts.setupTimeoutSeconds !== undefined ? { setup_timeout_seconds: opts.setupTimeoutSeconds } : {}),
159
207
  ...(opts.packages ? { packages: opts.packages } : {}),
@@ -179,18 +227,28 @@ export function Box(opts: BoxOpts): BoxResources {
179
227
  const allowedVaults =
180
228
  opts.allowedVaults === "any" ? undefined : (opts.allowedVaults ?? (vault ? [vault] : []));
181
229
 
230
+ // The resolved allowed-vaults value, for the read contract (see "Allowed
231
+ // vaults in the read contract" above) — "any" when the list is left unset,
232
+ // otherwise each entry's own declared name.
233
+ const allowedVaultsForReadContract: "any" | string[] =
234
+ opts.allowedVaults === "any"
235
+ ? "any"
236
+ : allowedVaults!.map((v) => (typeof v === "string" ? v : (v as unknown as { props: { name: string } }).props.name));
237
+ const agentMetadata = { ...metadata, [BOX_ALLOWED_VAULTS_METADATA_KEY]: allowedVaultsForReadContract };
238
+
182
239
  const agent = new Agent({
183
240
  name: opts.name,
184
241
  runtime: opts.runtime ?? "claude",
185
242
  ...(opts.model !== undefined ? { model: opts.model } : {}),
186
243
  sandbox_mode: "persistent",
244
+ ...(opts.sandboxProvider !== undefined ? { sandbox_provider: opts.sandboxProvider } : {}),
187
245
  environment,
188
246
  permission_policy: opts.permissionPolicy,
189
247
  ...(allowedVaults !== undefined ? { allowed_vault_ids: allowedVaults } : {}),
190
248
  ...(opts.skills ? { skills: opts.skills } : {}),
191
249
  ...(opts.mcpServers ? { mcp_servers: opts.mcpServers } : {}),
192
250
  ...(opts.system !== undefined ? { system: opts.system } : {}),
193
- metadata,
251
+ metadata: agentMetadata,
194
252
  });
195
253
 
196
254
  return { environment, agent, ...(vault ? { vault } : {}), port };
@@ -1,12 +1,13 @@
1
1
  import { beforeEach, describe, expect, it } from "vitest";
2
- import { WatchOp, type OpConfig } from "@intentius/chant/op";
2
+ import { WatchOp, isStewardDeclaration, stewardFormFor, type OpConfig } from "@intentius/chant/op";
3
3
  import { ConciergeStack } from "./concierge-stack";
4
4
  import { Steward, stewardForOp, __resetStewardsForTests } from "./steward";
5
- import { Box, BOX_PORT_METADATA_KEY } from "./box";
5
+ import { Box, BOX_ALLOWED_VAULTS_METADATA_KEY, BOX_PORT_METADATA_KEY } from "./box";
6
6
  import { postSynthChecks } from "../lint/post-synth/index";
7
7
  import spec from "../spec/fountain-openapi.snapshot.json";
8
8
  import { Environment, Vault } from "../generated/index";
9
9
  import { fountainSerializer } from "../serializer";
10
+ import { buildGraphIr, resolveAttrRefs } from "@intentius/chant";
10
11
  import type { Declarable } from "@intentius/chant";
11
12
  import type { PostSynthContext } from "@intentius/chant/lint/post-synth";
12
13
  import { runtimeModelValidCheck } from "../lint/post-synth/ftn016-runtime-model-valid";
@@ -238,6 +239,77 @@ describe("Steward", () => {
238
239
  });
239
240
  });
240
241
 
242
+ // ── Steward: the declaration core reads, and its form (#2731) ─────────────
243
+
244
+ describe("Steward declaration and form", () => {
245
+ beforeEach(() => {
246
+ __resetStewardsForTests();
247
+ });
248
+
249
+ const toolchain = () => new Environment({ name: "toolchain" });
250
+
251
+ it("returns the declaration core reads: name, every op, and fountain as the default form", () => {
252
+ const { declaration } = Steward({
253
+ name: "box-steward",
254
+ environment: toolchain(),
255
+ ops: [op("box-converge", { cron: "* * * * *" }), op("box-release")],
256
+ });
257
+ expect(isStewardDeclaration(declaration)).toBe(true);
258
+ expect(declaration.name).toBe("box-steward");
259
+ expect(declaration.ops.map((o) => o.name)).toEqual(["box-converge", "box-release"]);
260
+ expect(declaration.form).toEqual({ default: "fountain", environments: {} });
261
+ expect(stewardFormFor(declaration, "prod")).toBe("fountain");
262
+ });
263
+
264
+ it("names the environments where the same steward runs locally", () => {
265
+ const { declaration, schedules } = Steward({
266
+ name: "box-steward",
267
+ environment: toolchain(),
268
+ ops: [op("box-converge", { cron: "* * * * *" })],
269
+ form: { default: "fountain", environments: { minimal: "local", spritzer: "local" } },
270
+ });
271
+ expect(stewardFormFor(declaration, "fountain-k3d")).toBe("fountain");
272
+ expect(stewardFormFor(declaration, "minimal")).toBe("local");
273
+ expect(stewardFormFor(declaration, "spritzer")).toBe("local");
274
+ // The Fountain resources are declared whatever the form: which environment
275
+ // they run in is the deploy's business, not the declaration's.
276
+ expect(schedules).toHaveLength(1);
277
+ });
278
+
279
+ it("refuses a form that is local everywhere, which needs none of the Fountain resources", () => {
280
+ expect(() =>
281
+ Steward({ name: "box-steward", environment: toolchain(), ops: [], form: "local" }),
282
+ ).toThrow(/local" in every environment/);
283
+ });
284
+
285
+ it("refuses an op listed twice, which would run twice per fire", () => {
286
+ const converge = op("box-converge", { cron: "* * * * *" });
287
+ expect(() =>
288
+ Steward({ name: "box-steward", environment: toolchain(), ops: [converge, converge] }),
289
+ ).toThrow(/listed twice/);
290
+ });
291
+
292
+ it("records the brokered capabilities, and refuses them beside a vault (#2726)", () => {
293
+ const { declaration } = Steward({ name: "box-steward", environment: toolchain(), ops: [], capabilities: ["fountain"] });
294
+ expect(declaration.capabilities).toEqual(["fountain"]);
295
+ expect(declaration.vault).toBeNull();
296
+ __resetStewardsForTests();
297
+ expect(
298
+ Steward({ name: "prod-steward", environment: toolchain(), vault: new Vault({ name: "prod-creds" }), ops: [] }).declaration.vault,
299
+ ).toBe("prod-creds");
300
+ __resetStewardsForTests();
301
+ expect(() =>
302
+ Steward({ name: "x", environment: toolchain(), vault: new Vault({ name: "v" }), ops: [], capabilities: ["fountain"] }),
303
+ ).toThrow(/holds no credential/);
304
+ });
305
+
306
+ it("refuses an unknown form", () => {
307
+ expect(() =>
308
+ Steward({ name: "box-steward", environment: toolchain(), ops: [], form: "cloud" as never }),
309
+ ).toThrow(/"local" or "fountain"/);
310
+ });
311
+ });
312
+
241
313
  // ── Box ───────────────────────────────────────────────────────────────────
242
314
 
243
315
  describe("Box", () => {
@@ -267,13 +339,18 @@ describe("Box", () => {
267
339
  expect(a.environment).toBe(environment);
268
340
  expect(a.permission_policy).toEqual({ default: "auto_allow" });
269
341
  expect(a.allowed_vault_ids).toEqual([]);
270
- expect(a.metadata).toEqual({ "managed-by": "chant", "box-port": 8080 });
342
+ expect(a.sandbox_provider).toBeUndefined();
343
+ expect(a.metadata).toEqual({
344
+ "managed-by": "chant",
345
+ "box-port": 8080,
346
+ [BOX_ALLOWED_VAULTS_METADATA_KEY]: [],
347
+ });
271
348
 
272
349
  expect(vault).toBeUndefined();
273
350
  expect(port).toBe(8080);
274
351
  });
275
352
 
276
- it("declares the shared vault and scopes the agent to it", () => {
353
+ it("declares the shared vault and scopes the agent to it, named in the read contract", () => {
277
354
  const { agent, vault } = Box({
278
355
  ...base,
279
356
  vault: { secrets: [{ key: "STUDIO_SECRET", value: "${STUDIO_SECRET}" }] },
@@ -282,6 +359,18 @@ describe("Box", () => {
282
359
  expect(props(vault).secrets).toEqual([{ key: "STUDIO_SECRET", value: "${STUDIO_SECRET}" }]);
283
360
  expect(props(vault).metadata).toEqual({ "managed-by": "chant" });
284
361
  expect(props(agent).allowed_vault_ids).toEqual([vault]);
362
+ expect((props(agent).metadata as Record<string, unknown>)[BOX_ALLOWED_VAULTS_METADATA_KEY]).toEqual([
363
+ "studio-box-secrets",
364
+ ]);
365
+ });
366
+
367
+ it("resolves an explicit allowedVaults list to names in the read contract", () => {
368
+ const { agent } = Box({ ...base, allowedVaults: ["other-secrets", "studio-box-secrets"] });
369
+ expect(props(agent).allowed_vault_ids).toEqual(["other-secrets", "studio-box-secrets"]);
370
+ expect((props(agent).metadata as Record<string, unknown>)[BOX_ALLOWED_VAULTS_METADATA_KEY]).toEqual([
371
+ "other-secrets",
372
+ "studio-box-secrets",
373
+ ]);
285
374
  });
286
375
 
287
376
  it("loosening is a visible parameter", () => {
@@ -292,6 +381,7 @@ describe("Box", () => {
292
381
  port: 3000,
293
382
  unrestrictedNetworking: true,
294
383
  allowedVaults: "any",
384
+ sandboxProvider: "e2b",
295
385
  repo: { url: "https://github.com/o/app", mountPath: "/srv/app", ref: "v1", secretKey: "GH_TOKEN" },
296
386
  envVars: { NODE_ENV: "production" },
297
387
  packages: { apt: ["podman"] },
@@ -309,14 +399,32 @@ describe("Box", () => {
309
399
  const a = props(open.agent);
310
400
  expect(a.runtime).toBe("codex");
311
401
  expect(a.model).toBe("openai/gpt-5");
312
- // "any" leaves the list unset: fountain reads null as any vault the tenant owns.
402
+ expect(a.sandbox_provider).toBe("e2b");
403
+ // "any" leaves the manifest's own list unset: fountain reads that as any
404
+ // vault the tenant owns. The read contract still names it explicitly.
313
405
  expect("allowed_vault_ids" in a).toBe(false);
406
+ expect((a.metadata as Record<string, unknown>)[BOX_ALLOWED_VAULTS_METADATA_KEY]).toBe("any");
314
407
  expect(open.port).toBe(3000);
315
408
 
316
409
  const listed = Box({ ...base, allowedHosts: ["registry.npmjs.org"] });
317
410
  expect(props(listed.environment).networking_config).toEqual({ allowed_hosts: ["registry.npmjs.org"] });
318
411
  });
319
412
 
413
+ it("builds with no repo — no clone, the Environment declares no repositories", () => {
414
+ const { environment, agent, port } = Box({
415
+ name: "blank-slate",
416
+ setupScript: "#!/bin/bash\nnpm install\n",
417
+ permissionPolicy: { default: "auto_allow" as const },
418
+ sandboxProvider: "runner",
419
+ });
420
+ const env = props(environment);
421
+ expect("repositories" in env).toBe(false);
422
+ expect(env.setup_script).toBe("#!/bin/bash\nnpm install\n");
423
+ const a = props(agent);
424
+ expect(a.sandbox_provider).toBe("runner");
425
+ expect(port).toBe(8080);
426
+ });
427
+
320
428
  it("refuses what it cannot mean", () => {
321
429
  expect(() => Box({ ...base, port: 0 })).toThrow(/not a TCP port/);
322
430
  expect(() => Box({ ...base, port: 8080.5 })).toThrow(/not a TCP port/);
@@ -366,4 +474,62 @@ describe("Box", () => {
366
474
  const diagnostics = postSynthChecks.flatMap((c) => c.check(ctx));
367
475
  expect(diagnostics).toEqual([]);
368
476
  });
477
+
478
+ it("a no-repo, sandboxProvider: runner box builds a manifest whose every field exists in the pinned spec, clean under every post-synth check", () => {
479
+ const { environment, agent } = Box({
480
+ name: "blank-slate",
481
+ setupScript: "#!/bin/bash\nnpm install\n",
482
+ permissionPolicy: { default: "auto_allow" as const },
483
+ sandboxProvider: "runner",
484
+ });
485
+ const entities = new Map<string, Declarable>([
486
+ ["boxAgent", agent as unknown as Declarable],
487
+ ["boxEnv", environment as unknown as Declarable],
488
+ ]);
489
+
490
+ const yaml = fountainSerializer.serialize(entities) as string;
491
+ expect([...yaml.matchAll(/^kind: (\w+)$/gm)].map((m) => m[1])).toEqual(["Environment", "Agent"]);
492
+ expect(yaml).not.toContain("repositories:");
493
+ expect(yaml).toContain("sandbox_provider: runner");
494
+
495
+ const schemas = (spec as unknown as { components: { schemas: Record<string, { properties: Record<string, unknown> }> } })
496
+ .components.schemas;
497
+ const manifestOnly = new Set(["environment", "secrets"]);
498
+ for (const [kind, entity] of [
499
+ ["EnvironmentRequest", environment],
500
+ ["AgentRequest", agent],
501
+ ] as const) {
502
+ const fields = Object.keys(schemas[kind].properties);
503
+ for (const key of Object.keys(props(entity))) {
504
+ if (key === "name" || manifestOnly.has(key)) continue;
505
+ expect(fields, `${kind} has no field ${key}`).toContain(key);
506
+ }
507
+ }
508
+
509
+ const ctx = { outputs: new Map(), entities, buildResult: { warnings: [], errors: [] } } as unknown as PostSynthContext;
510
+ const diagnostics = postSynthChecks.flatMap((c) => c.check(ctx));
511
+ expect(diagnostics).toEqual([]);
512
+ });
513
+
514
+ it("shows the agent's sandbox provider and allowed vaults on the graph node (#2759)", () => {
515
+ const { agent, environment } = Box({
516
+ ...base,
517
+ sandboxProvider: "runner",
518
+ allowedVaults: "any",
519
+ });
520
+ const entities = new Map<string, Declarable>([
521
+ ["boxAgent", agent as unknown as Declarable],
522
+ ["boxEnv", environment as unknown as Declarable],
523
+ ]);
524
+ resolveAttrRefs(entities);
525
+
526
+ const ir = buildGraphIr(entities);
527
+ const agentNode = ir.nodes.find((n) => n.id === "boxAgent")!;
528
+ expect(agentNode.attrs.sandbox_provider).toBe("runner");
529
+ // "any" leaves the manifest's allowed_vault_ids unset, so it's the
530
+ // metadata marker a graph reader checks, not an absent key it has to
531
+ // interpret.
532
+ expect(agentNode.attrs.allowed_vault_ids).toBeUndefined();
533
+ expect((agentNode.attrs.metadata as Record<string, unknown>)[BOX_ALLOWED_VAULTS_METADATA_KEY]).toBe("any");
534
+ });
369
535
  });
@@ -44,8 +44,44 @@
44
44
  * the server does not keep. And a webhook url FTN022 would reject is refused
45
45
  * here rather than at synth, because a composite that constructed it would be
46
46
  * handing the author a lint error about a resource they never typed.
47
+ *
48
+ * ## One steward whatever the preset (#2731)
49
+ *
50
+ * The composite also returns `declaration`, the steward as core reads it
51
+ * (`declareSteward` in `@intentius/chant/op`): its name, its Ops and its
52
+ * `form`. A box whose preset has no Fountain in some environment (a docker
53
+ * compose box, a Sprites box) sets `form` so that environment is `local`, and
54
+ * there `chant operator --steward <name>` runs the same Ops on the same crons,
55
+ * one at a time, each under its operator lease and the whole process under the
56
+ * steward's own. `chant operator --steward` refuses an environment whose form
57
+ * is `fountain`, because this Agent and Teammate are the steward there. Export
58
+ * `declaration` from an `*.op.ts` file so the operator and `chant workspace
59
+ * status` find it.
60
+ *
61
+ * ## Credentials (#2726)
62
+ *
63
+ * A box's steward holds no credential. Name the box capabilities its Ops use
64
+ * in `capabilities` (the names the member's `box` block in the workspace
65
+ * declaration gives them) and leave `vault` out: the broker holds the key and
66
+ * enforces the scope. `vault` together with `capabilities` is refused.
67
+ * `chant workspace check` fails a box member whose files hold a literal secret
68
+ * (`box-credential-declared`), and `chant workspace status --json` lists each
69
+ * capability the steward names with the broker the box block gives it.
70
+ *
71
+ * ## The coding agent beside it
72
+ *
73
+ * A box also has a coding agent editing the app. They share the checkout by
74
+ * writing different things. The steward's writes are chant writes: run,
75
+ * converge and gate records on the `chant/lifecycle` branch and lease refs
76
+ * under `refs/chant/lease/`, made with git plumbing that never reads or writes
77
+ * the index or the working tree. The app's files, the index and commits on the
78
+ * working branch are the coding agent's. An Op a steward runs does not edit the
79
+ * app. A change to the checkout that an Op has to make (applying a build, a
80
+ * workspace upgrade) belongs under the work item's lease (ws-055), on a branch
81
+ * of its own, never in the coding agent's working tree.
47
82
  */
48
83
 
84
+ import { declareSteward, type StewardDeclaration, type StewardFormSpec } from "@intentius/chant/op";
49
85
  import type { OpConfig, OpResource } from "@intentius/chant/op";
50
86
  import { Agent, Environment, Schedule, Teammate, Vault, Webhook } from "../generated/index";
51
87
  import { isPrivateHost } from "../lint/post-synth/ftn022-webhook-url-public-https";
@@ -84,8 +120,18 @@ export interface StewardOpts {
84
120
  name: string;
85
121
  /** The environment its computer is provisioned from: repo, chant, tooling. */
86
122
  environment: InstanceType<typeof Environment>;
87
- /** Secrets layered on top. Omit under the egress broker, which holds them. */
123
+ /**
124
+ * Secrets layered on top, for a steward that isn't behind a broker. Omit it
125
+ * under a broker, which holds them, and name `capabilities` instead.
126
+ */
88
127
  vault?: InstanceType<typeof Vault>;
128
+ /**
129
+ * The box capabilities this steward's Ops reach through the box's broker
130
+ * (#2726), by the names the member's `box` block declares, such as
131
+ * `fountain` or `inference`. Refused together with `vault`: a brokered
132
+ * steward holds no credential.
133
+ */
134
+ capabilities?: string[];
89
135
  /**
90
136
  * The ops this steward runs. An op with a `schedule` gets a `Schedule`; one
91
137
  * without is still listed, so `chant run <op> --on fountain` knows which
@@ -95,6 +141,16 @@ export interface StewardOpts {
95
141
  webhook?: StewardWebhookOpts;
96
142
  /** Extra metadata merged over the `managed-by` marker on the Agent. */
97
143
  metadata?: Record<string, unknown>;
144
+ /**
145
+ * Where this steward runs (#2731). Default `fountain`: this composite's
146
+ * Agent and Teammate are the steward. A box whose preset has no Fountain in
147
+ * some environment names it here, e.g. `{ default: "fountain", environments:
148
+ * { minimal: "local" } }`, and `chant operator --steward <name> --env minimal`
149
+ * runs the same Ops there. The Fountain resources are declared either way; a
150
+ * form that is `local` everywhere is refused, since it needs none of them
151
+ * (use core's `declareSteward` for that).
152
+ */
153
+ form?: StewardFormSpec;
98
154
  }
99
155
 
100
156
  export interface StewardResources {
@@ -103,6 +159,12 @@ export interface StewardResources {
103
159
  /** One per op that carries a `schedule`, in `ops` order. */
104
160
  schedules: InstanceType<typeof Schedule>[];
105
161
  webhook?: InstanceType<typeof Webhook>;
162
+ /**
163
+ * The steward as core reads it (#2731): name, Ops and form. Export it from
164
+ * an `*.op.ts` file so `chant operator --steward` and `chant workspace
165
+ * status` find it.
166
+ */
167
+ declaration: StewardDeclaration;
106
168
  }
107
169
 
108
170
  /** The command an `acp` agent speaks the protocol over (#2125). */
@@ -195,6 +257,23 @@ export function Steward(opts: StewardOpts): StewardResources {
195
257
 
196
258
  if (opts.webhook) checkWebhookUrl(opts.name, opts.webhook.url);
197
259
 
260
+ // The declaration core reads. Built here, before any resource, so a bad
261
+ // name, a duplicate op or an unknown form is refused with nothing declared.
262
+ const declaration = declareSteward({
263
+ name: opts.name,
264
+ ops,
265
+ form: opts.form ?? "fountain",
266
+ ...(opts.capabilities ? { capabilities: opts.capabilities } : {}),
267
+ ...(opts.vault ? { vault: vaultName } : {}),
268
+ });
269
+ const envForms = [declaration.form.default, ...Object.values(declaration.form.environments)];
270
+ if (!envForms.includes("fountain")) {
271
+ throw new Error(
272
+ `Steward "${opts.name}": form is "local" in every environment, so none of the Fountain resources this ` +
273
+ `composite declares would ever run. Declare it with declareSteward from @intentius/chant/op instead.`,
274
+ );
275
+ }
276
+
198
277
  const agent = new Agent({
199
278
  name: opts.name,
200
279
  runtime: "acp",
@@ -247,5 +326,5 @@ export function Steward(opts: StewardOpts): StewardResources {
247
326
  bindings.set(binding, opts.name);
248
327
  for (const op of ops) opStewards.set(op.name, opts.name);
249
328
 
250
- return { agent, teammate, schedules, ...(webhook ? { webhook } : {}) };
329
+ return { agent, teammate, schedules, ...(webhook ? { webhook } : {}), declaration };
251
330
  }
package/src/index.ts CHANGED
@@ -41,8 +41,15 @@ export { ConciergeStack } from "./composites/concierge-stack";
41
41
  export type { ConciergeStackOpts, ConciergeStackResources } from "./composites/concierge-stack";
42
42
  export { Steward, stewardForOp, STEWARD_RUNTIME_COMMAND } from "./composites/steward";
43
43
  export type { StewardOp, StewardOpts, StewardResources, StewardWebhookOpts } from "./composites/steward";
44
- export { Box, BOX_PORT_METADATA_KEY, BOX_DEFAULT_PORT, BOX_DEFAULT_MOUNT_PATH } from "./composites/box";
45
- export type { BoxOpts, BoxResources, BoxRepositoryOpts, BoxVaultOpts, BoxPermissionPolicy } from "./composites/box";
44
+ export {
45
+ Box,
46
+ BOX_PORT_METADATA_KEY,
47
+ BOX_ALLOWED_VAULTS_METADATA_KEY,
48
+ BOX_DEFAULT_PORT,
49
+ BOX_DEFAULT_MOUNT_PATH,
50
+ BOX_SANDBOX_PROVIDERS,
51
+ } from "./composites/box";
52
+ export type { BoxOpts, BoxResources, BoxRepositoryOpts, BoxVaultOpts, BoxPermissionPolicy, BoxSandboxProvider } from "./composites/box";
46
53
 
47
54
  // `chant acp` (#2125) — the ACP server, mounted through the plugin's command
48
55
  // group. Exported so an embedder can serve it over its own transport.