@usenaive-sdk/cli 0.12.2 → 0.13.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.
- package/README.md +76 -5
- package/dist/client.d.ts +74 -1
- package/dist/client.js +351 -17
- package/dist/client.js.map +1 -1
- package/dist/commands/aeo.js +69 -27
- package/dist/commands/aeo.js.map +1 -1
- package/dist/commands/app-data.js +29 -11
- package/dist/commands/app-data.js.map +1 -1
- package/dist/commands/apps.d.ts +7 -0
- package/dist/commands/apps.js +255 -18
- package/dist/commands/apps.js.map +1 -1
- package/dist/commands/audio.d.ts +9 -0
- package/dist/commands/audio.js +391 -0
- package/dist/commands/audio.js.map +1 -0
- package/dist/commands/auth.d.ts +31 -0
- package/dist/commands/auth.js +403 -0
- package/dist/commands/auth.js.map +1 -0
- package/dist/commands/billing.js +12 -12
- package/dist/commands/billing.js.map +1 -1
- package/dist/commands/brain.d.ts +2 -0
- package/dist/commands/brain.js +1255 -0
- package/dist/commands/brain.js.map +1 -0
- package/dist/commands/browser.js +35 -8
- package/dist/commands/browser.js.map +1 -1
- package/dist/commands/business.js +117 -16
- package/dist/commands/business.js.map +1 -1
- package/dist/commands/clone.d.ts +2 -0
- package/dist/commands/clone.js +203 -0
- package/dist/commands/clone.js.map +1 -0
- package/dist/commands/companies.d.ts +8 -0
- package/dist/commands/companies.js +137 -47
- package/dist/commands/companies.js.map +1 -1
- package/dist/commands/company-data.d.ts +2 -0
- package/dist/commands/company-data.js +168 -0
- package/dist/commands/company-data.js.map +1 -0
- package/dist/commands/compute.js +10 -3
- package/dist/commands/compute.js.map +1 -1
- package/dist/commands/config.d.ts +2 -0
- package/dist/commands/config.js +36 -0
- package/dist/commands/config.js.map +1 -0
- package/dist/commands/connections.d.ts +6 -0
- package/dist/commands/connections.js +29 -1
- package/dist/commands/connections.js.map +1 -1
- package/dist/commands/domains.js +1 -1
- package/dist/commands/ecommerce.js +19 -9
- package/dist/commands/ecommerce.js.map +1 -1
- package/dist/commands/email.js +56 -9
- package/dist/commands/email.js.map +1 -1
- package/dist/commands/env.d.ts +3 -3
- package/dist/commands/env.js +68 -4
- package/dist/commands/env.js.map +1 -1
- package/dist/commands/formation.js +3 -3
- package/dist/commands/identity.js +4 -3
- package/dist/commands/identity.js.map +1 -1
- package/dist/commands/images.js +87 -13
- package/dist/commands/images.js.map +1 -1
- package/dist/commands/init.d.ts +3 -0
- package/dist/commands/init.js +39 -36
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/jobs.js +1 -0
- package/dist/commands/jobs.js.map +1 -1
- package/dist/commands/keys.js +26 -3
- package/dist/commands/keys.js.map +1 -1
- package/dist/commands/legacy-auth.d.ts +27 -0
- package/dist/commands/legacy-auth.js +64 -0
- package/dist/commands/legacy-auth.js.map +1 -0
- package/dist/commands/llm.js +13 -0
- package/dist/commands/llm.js.map +1 -1
- package/dist/commands/login.js +2 -0
- package/dist/commands/login.js.map +1 -1
- package/dist/commands/loops.d.ts +2 -0
- package/dist/commands/loops.js +260 -0
- package/dist/commands/loops.js.map +1 -0
- package/dist/commands/media.js +3 -3
- package/dist/commands/media.js.map +1 -1
- package/dist/commands/mobile.d.ts +4 -0
- package/dist/commands/mobile.js +382 -0
- package/dist/commands/mobile.js.map +1 -0
- package/dist/commands/payments.d.ts +2 -0
- package/dist/commands/payments.js +131 -0
- package/dist/commands/payments.js.map +1 -0
- package/dist/commands/people.d.ts +2 -0
- package/dist/commands/people.js +193 -0
- package/dist/commands/people.js.map +1 -0
- package/dist/commands/phone.js +3 -3
- package/dist/commands/phone.js.map +1 -1
- package/dist/commands/projects.d.ts +13 -0
- package/dist/commands/projects.js +220 -0
- package/dist/commands/projects.js.map +1 -0
- package/dist/commands/register.js +16 -8
- package/dist/commands/register.js.map +1 -1
- package/dist/commands/sandbox.d.ts +2 -0
- package/dist/commands/sandbox.js +245 -0
- package/dist/commands/sandbox.js.map +1 -0
- package/dist/commands/search.js +26 -12
- package/dist/commands/search.js.map +1 -1
- package/dist/commands/seo.js +16 -8
- package/dist/commands/seo.js.map +1 -1
- package/dist/commands/sessions.d.ts +6 -0
- package/dist/commands/sessions.js +109 -0
- package/dist/commands/sessions.js.map +1 -0
- package/dist/commands/social-data.d.ts +2 -0
- package/dist/commands/social-data.js +244 -0
- package/dist/commands/social-data.js.map +1 -0
- package/dist/commands/social.js +8 -6
- package/dist/commands/social.js.map +1 -1
- package/dist/commands/teams.d.ts +69 -0
- package/dist/commands/teams.js +503 -0
- package/dist/commands/teams.js.map +1 -0
- package/dist/commands/up.d.ts +150 -0
- package/dist/commands/up.js +415 -15
- package/dist/commands/up.js.map +1 -1
- package/dist/commands/usage.js +15 -4
- package/dist/commands/usage.js.map +1 -1
- package/dist/commands/use.d.ts +9 -0
- package/dist/commands/use.js +76 -11
- package/dist/commands/use.js.map +1 -1
- package/dist/commands/users.d.ts +8 -0
- package/dist/commands/users.js +28 -9
- package/dist/commands/users.js.map +1 -1
- package/dist/commands/vault.js +28 -1
- package/dist/commands/vault.js.map +1 -1
- package/dist/commands/video.js +190 -270
- package/dist/commands/video.js.map +1 -1
- package/dist/commands/voice.d.ts +2 -0
- package/dist/commands/voice.js +516 -0
- package/dist/commands/voice.js.map +1 -0
- package/dist/commands/wallet.d.ts +2 -0
- package/dist/commands/wallet.js +244 -0
- package/dist/commands/wallet.js.map +1 -0
- package/dist/commands/whoami.js +17 -1
- package/dist/commands/whoami.js.map +1 -1
- package/dist/config.d.ts +24 -1
- package/dist/config.js +36 -7
- package/dist/config.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +290 -20
- package/dist/index.js.map +1 -1
- package/dist/lib/brain-select.d.ts +193 -0
- package/dist/lib/brain-select.js +403 -0
- package/dist/lib/brain-select.js.map +1 -0
- package/dist/lib/cli-validation.d.ts +12 -0
- package/dist/lib/cli-validation.js +46 -0
- package/dist/lib/cli-validation.js.map +1 -0
- package/dist/lib/deprecation.d.ts +158 -0
- package/dist/lib/deprecation.js +140 -0
- package/dist/lib/deprecation.js.map +1 -0
- package/dist/lib/duration.d.ts +8 -0
- package/dist/lib/duration.js +11 -0
- package/dist/lib/duration.js.map +1 -0
- package/dist/lib/http-response.d.ts +11 -0
- package/dist/lib/http-response.js +96 -0
- package/dist/lib/http-response.js.map +1 -0
- package/dist/lib/pricing.d.ts +132 -0
- package/dist/lib/pricing.js +148 -0
- package/dist/lib/pricing.js.map +1 -0
- package/dist/lib/stream-frames.d.ts +114 -0
- package/dist/lib/stream-frames.js +169 -0
- package/dist/lib/stream-frames.js.map +1 -0
- package/dist/lib/teams-catalog.d.ts +141 -0
- package/dist/lib/teams-catalog.js +765 -0
- package/dist/lib/teams-catalog.js.map +1 -0
- package/dist/lib/unknown-command.d.ts +57 -0
- package/dist/lib/unknown-command.js +93 -0
- package/dist/lib/unknown-command.js.map +1 -0
- package/dist/lib/version-check.d.ts +26 -0
- package/dist/lib/version-check.js +21 -0
- package/dist/lib/version-check.js.map +1 -0
- package/dist/output.d.ts +119 -10
- package/dist/output.js +309 -15
- package/dist/output.js.map +1 -1
- package/dist/prompt.d.ts +17 -0
- package/dist/prompt.js +70 -0
- package/dist/prompt.js.map +1 -0
- package/package.json +11 -6
- package/dist/commands/billing-client.d.ts +0 -2
- package/dist/commands/billing-client.js +0 -271
- package/dist/commands/billing-client.js.map +0 -1
- package/dist/commands/crm-app.d.ts +0 -2
- package/dist/commands/crm-app.js +0 -306
- package/dist/commands/crm-app.js.map +0 -1
- package/dist/commands/crm.d.ts +0 -2
- package/dist/commands/crm.js +0 -319
- package/dist/commands/crm.js.map +0 -1
- package/dist/commands/integrations.d.ts +0 -2
- package/dist/commands/integrations.js +0 -199
- package/dist/commands/integrations.js.map +0 -1
- package/dist/commands/link.d.ts +0 -3
- package/dist/commands/link.js +0 -91
- package/dist/commands/link.js.map +0 -1
- package/dist/commands/operators.d.ts +0 -11
- package/dist/commands/operators.js +0 -120
- package/dist/commands/operators.js.map +0 -1
package/dist/commands/up.d.ts
CHANGED
|
@@ -1,3 +1,153 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
|
+
import { type NextStep } from "../output.js";
|
|
3
|
+
/** What the API reports about the brains `company.brains` declared. */
|
|
4
|
+
interface BrainsResult {
|
|
5
|
+
declared_in?: string | null;
|
|
6
|
+
created?: Array<{
|
|
7
|
+
name: string;
|
|
8
|
+
}>;
|
|
9
|
+
converged?: Array<{
|
|
10
|
+
name: string;
|
|
11
|
+
}>;
|
|
12
|
+
default?: {
|
|
13
|
+
name?: string | null;
|
|
14
|
+
changed?: boolean;
|
|
15
|
+
};
|
|
16
|
+
retained_undeclared?: string[];
|
|
17
|
+
/**
|
|
18
|
+
* Declared brain policy with no column to land in, per brain.
|
|
19
|
+
* `brain_knowledge_bases` has columns for name, is_default, scope, status and
|
|
20
|
+
* provider ids and nothing else, so `retention`, `partitions`, `writes`,
|
|
21
|
+
* `recall`, `lanes`, `visibility` and `bounds` are accepted and not stored.
|
|
22
|
+
*/
|
|
23
|
+
unpersisted?: Record<string, string[]>;
|
|
24
|
+
/** The server's own prose for the same, plus the retained-undeclared note. */
|
|
25
|
+
notes?: string[];
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* The brain half of the report, or undefined when the config declared none.
|
|
29
|
+
*
|
|
30
|
+
* Exported for the same reason it exists: `naive up` printed `applied: true` and
|
|
31
|
+
* said NOTHING about brains, so a config that declared two of them looked
|
|
32
|
+
* identical to a config that declared none. `--plan` showed them (it prints the
|
|
33
|
+
* whole plan); apply did not. A provisioned thing that is not reported is the
|
|
34
|
+
* same silence as an unprovisioned one, one level up.
|
|
35
|
+
*
|
|
36
|
+
* 🔴 AND `unpersisted` IS PART OF THE REPORT, NOT AN EXTRA. The apply path
|
|
37
|
+
* computed the accept-and-drop list and this projection threw it away, so
|
|
38
|
+
* `naive up --plan` told the user which declared brain keys have no column and
|
|
39
|
+
* plain `naive up` — the command people actually run — did not. That is the same
|
|
40
|
+
* defect one level down: `packages/api/src/services/deployment-brains.ts`'s own
|
|
41
|
+
* header calls accept-and-drop "the exact defect this file exists to remove", and
|
|
42
|
+
* a report only the dry run prints is a report most operators never see.
|
|
43
|
+
*/
|
|
44
|
+
export declare function brainReport(brains: BrainsResult | undefined): {
|
|
45
|
+
declared_in: string | null;
|
|
46
|
+
created: string[];
|
|
47
|
+
converged: string[];
|
|
48
|
+
default: string | null;
|
|
49
|
+
retained: string[];
|
|
50
|
+
unpersisted?: Record<string, string[]>;
|
|
51
|
+
notes?: string[];
|
|
52
|
+
} | undefined;
|
|
53
|
+
/** What the plan reports per declared team, whichever runtime it asked for. */
|
|
54
|
+
interface TeamPlanEntry {
|
|
55
|
+
name: string;
|
|
56
|
+
runtime: string;
|
|
57
|
+
roles?: string[];
|
|
58
|
+
lead_role?: string;
|
|
59
|
+
/** Role → runtime, present only for a MIXED team. See `role_runtimes` on the API. */
|
|
60
|
+
role_runtimes?: Record<string, string>;
|
|
61
|
+
placement?: {
|
|
62
|
+
status: string;
|
|
63
|
+
code?: string;
|
|
64
|
+
message?: string;
|
|
65
|
+
};
|
|
66
|
+
/** Keys this team declared that NOTHING on the control plane reads. */
|
|
67
|
+
unconsumed?: string[];
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* The per-team accept-and-drop warning, one line per team that declared any.
|
|
71
|
+
*
|
|
72
|
+
* The data has been on the wire the whole time (`plan.teams[].unconsumed`,
|
|
73
|
+
* computed by `unconsumedTeamKeys` in packages/api) and `naive up` printed none
|
|
74
|
+
* of it — `team({ governance: {…} })` applied green and enforced nothing, with
|
|
75
|
+
* nothing anywhere for the author to notice. Printed on the PLAN and on the
|
|
76
|
+
* APPLY, because a report only the dry run shows is a report most operators
|
|
77
|
+
* never see.
|
|
78
|
+
*/
|
|
79
|
+
export declare function unconsumedWarnings(teams: readonly TeamPlanEntry[]): string[];
|
|
80
|
+
/**
|
|
81
|
+
* ONE deprecation line when the config still uses the legacy ECS surface —
|
|
82
|
+
* `runtime.pool()` declarations, an agent-level `runtime: "<pool>"` string, or
|
|
83
|
+
* `systems:`. All three keep working indefinitely; the line names what was
|
|
84
|
+
* found and where new configs should author instead.
|
|
85
|
+
*/
|
|
86
|
+
export declare function legacyDeprecationNotice(config: Record<string, unknown>): string | null;
|
|
87
|
+
/**
|
|
88
|
+
* ONE rename line when the config declares `agents:`. Separate from the legacy
|
|
89
|
+
* line above on purpose: `runtime.pool()` / `systems:` are a deprecated
|
|
90
|
+
* SURFACE, while `agents:` is a deprecated SPELLING — the block compiles to
|
|
91
|
+
* byte-for-byte what `kits:` compiles to, so the honest notice is "rename",
|
|
92
|
+
* not "migrate". A notice, never a refusal, on both entrypoints.
|
|
93
|
+
*/
|
|
94
|
+
export declare function agentsRenameNotice(config: Record<string, unknown>): string | null;
|
|
95
|
+
/**
|
|
96
|
+
* The operator credential the DURABLE RUNTIME issued, read from the ENVIRONMENT.
|
|
97
|
+
*
|
|
98
|
+
* Never from `naive.config.ts`, and that is the whole point rather than a style
|
|
99
|
+
* choice. The config is committed, so a credential in it is a credential in git —
|
|
100
|
+
* and, more importantly, the config is the one thing that must not by itself be
|
|
101
|
+
* able to change where a tenant runs. Pointing a tenant at the durable runtime
|
|
102
|
+
* overwrites its `sidecar_url` and `container_auth_token` and nulls its
|
|
103
|
+
* `instance_id`, none of which is stashed anywhere. Requiring a second, separate
|
|
104
|
+
* act means an edit alone can never do it.
|
|
105
|
+
*
|
|
106
|
+
* `NAIVE_DURABLE_CREDENTIAL_<TEAM>` names one team; `NAIVE_DURABLE_CREDENTIAL`
|
|
107
|
+
* covers them all. Neither is ever printed, and the API never echoes it back.
|
|
108
|
+
*/
|
|
109
|
+
export declare function durableCredentialEnvVar(team: string): string;
|
|
110
|
+
/**
|
|
111
|
+
* The recovery for ONE refused team — a command that EXISTS, or none.
|
|
112
|
+
*
|
|
113
|
+
* 🔴 `naive runtime migrate` IS NOT A COMMAND, AND THIS USED TO PRINT IT.
|
|
114
|
+
* Every refusal here used to emit
|
|
115
|
+
* `naive runtime migrate --tenant … --to durable --credential-from <file>`.
|
|
116
|
+
* Measured on the built CLI: `naive runtime migrate --tenant <id> --to hermes`
|
|
117
|
+
* answers
|
|
118
|
+
* `{"code":"cli_outdated","message":"Unknown command 'runtime'. Your naive CLI
|
|
119
|
+
* is v0.12.4; this command may exist in a newer version."}` — and it exists in
|
|
120
|
+
* NO version: `packages/cli/src/index.ts` registers no `runtime` group at all.
|
|
121
|
+
* So an operator whose apply was refused was handed a command that tells them to
|
|
122
|
+
* upgrade, forever. A next_step that cannot be run is worse than none, because it
|
|
123
|
+
* ends the search.
|
|
124
|
+
*
|
|
125
|
+
* The two cases are genuinely different and are answered differently:
|
|
126
|
+
*
|
|
127
|
+
* · `runtime_credential_required` — there IS a procedure, it works today, and
|
|
128
|
+
* it is the one this command already implements: put the operator credential
|
|
129
|
+
* the durable runtime issued in the environment and re-run. Never in
|
|
130
|
+
* `naive.config.ts`, which is committed.
|
|
131
|
+
* · everything else (`runtime_migration_requires_explicit_opt_in`,
|
|
132
|
+
* `runtime_claim_in_flight`, `runtime_placement_lost_race`,
|
|
133
|
+
* `runtime_not_configured`, `runtime_state_unknown`) — there is NO self-serve
|
|
134
|
+
* command on this build. Moving an existing tenant between runtimes
|
|
135
|
+
* overwrites `sidecar_url` and `container_auth_token` and nulls `instance_id`
|
|
136
|
+
* with nothing stashed, so the control plane refuses it and no client can do
|
|
137
|
+
* it either. Say that, and print the server's own message, rather than
|
|
138
|
+
* inventing a command to carry the blame.
|
|
139
|
+
*
|
|
140
|
+
* The API writes the same vapour into `details.migrate_command`
|
|
141
|
+
* (packages/api/src/services/placement.ts MIGRATE_COMMAND). That string is not
|
|
142
|
+
* this package's to change and is reported as a follow-up; this function is why
|
|
143
|
+
* `naive up` no longer repeats it.
|
|
144
|
+
*/
|
|
145
|
+
export declare function refusalNextStep(t: {
|
|
146
|
+
name: string;
|
|
147
|
+
placement?: {
|
|
148
|
+
code?: string;
|
|
149
|
+
};
|
|
150
|
+
}): NextStep | null;
|
|
2
151
|
export declare const upCmd: Command;
|
|
3
152
|
export declare const downCmd: Command;
|
|
153
|
+
export {};
|
package/dist/commands/up.js
CHANGED
|
@@ -32,25 +32,63 @@ function collectWebFiles(root) {
|
|
|
32
32
|
return out;
|
|
33
33
|
}
|
|
34
34
|
const CONFIG_CANDIDATES = ["naive.config.ts", "naive.config.js", "naive.config.mjs", "naive.config.json"];
|
|
35
|
+
/**
|
|
36
|
+
* A module-resolution failure, as opposed to the config refusing itself.
|
|
37
|
+
*
|
|
38
|
+
* `ERR_MODULE_NOT_FOUND` / `MODULE_NOT_FOUND` is what an absent dependency
|
|
39
|
+
* raises. Matched on the CODE rather than the message, because a define-time
|
|
40
|
+
* refusal is free to mention a package name in its prose and must not be
|
|
41
|
+
* misread as a missing install.
|
|
42
|
+
*/
|
|
43
|
+
function isMissingModule(e) {
|
|
44
|
+
const code = e?.code;
|
|
45
|
+
return code === "ERR_MODULE_NOT_FOUND" || code === "MODULE_NOT_FOUND";
|
|
46
|
+
}
|
|
35
47
|
/** Evaluate naive.config.{ts,js,json} to its full object (the whole NaiveConfig). */
|
|
36
48
|
async function loadConfig(path) {
|
|
37
49
|
if (path.endsWith(".json")) {
|
|
38
50
|
try {
|
|
39
|
-
return JSON.parse(readFileSync(path, "utf8"));
|
|
51
|
+
return { ok: true, config: JSON.parse(readFileSync(path, "utf8")) };
|
|
40
52
|
}
|
|
41
|
-
catch {
|
|
42
|
-
|
|
53
|
+
catch (e) {
|
|
54
|
+
// A committed .json needs no toolchain, so a parse failure is only ever
|
|
55
|
+
// the file's own fault.
|
|
56
|
+
return { ok: false, reason: "invalid", message: e instanceof Error ? e.message : String(e) };
|
|
43
57
|
}
|
|
44
58
|
}
|
|
59
|
+
let jiti;
|
|
45
60
|
try {
|
|
46
61
|
const { createJiti } = await import("jiti");
|
|
47
|
-
|
|
62
|
+
jiti = createJiti(pathToFileURL(path).href);
|
|
63
|
+
}
|
|
64
|
+
catch (e) {
|
|
65
|
+
return {
|
|
66
|
+
ok: false,
|
|
67
|
+
reason: "unloadable",
|
|
68
|
+
message: e instanceof Error ? e.message : String(e),
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
try {
|
|
48
72
|
const mod = (await jiti.import(path));
|
|
49
73
|
const cfg = mod?.default ?? mod;
|
|
50
|
-
|
|
74
|
+
if (!cfg || typeof cfg !== "object") {
|
|
75
|
+
return {
|
|
76
|
+
ok: false,
|
|
77
|
+
reason: "invalid",
|
|
78
|
+
message: `${path} evaluated to ${cfg === undefined ? "undefined" : typeof cfg}. It must \`export default defineConfig({...})\`.`,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
return { ok: true, config: cfg };
|
|
51
82
|
}
|
|
52
|
-
catch {
|
|
53
|
-
|
|
83
|
+
catch (e) {
|
|
84
|
+
// The config itself threw. Almost always a NAMED define-time refusal, whose
|
|
85
|
+
// message is written to be read by the person who wrote the config — so it
|
|
86
|
+
// is surfaced verbatim rather than replaced with a guess.
|
|
87
|
+
return {
|
|
88
|
+
ok: false,
|
|
89
|
+
reason: isMissingModule(e) ? "unloadable" : "invalid",
|
|
90
|
+
message: e instanceof Error ? e.message : String(e),
|
|
91
|
+
};
|
|
54
92
|
}
|
|
55
93
|
}
|
|
56
94
|
/** Extract top-level keys inside a named object block, e.g. agentProfiles: { a, b }. */
|
|
@@ -92,6 +130,183 @@ function extractKeys(source, section) {
|
|
|
92
130
|
function findConfig() {
|
|
93
131
|
return CONFIG_CANDIDATES.map((c) => join(process.cwd(), c)).find(existsSync) ?? null;
|
|
94
132
|
}
|
|
133
|
+
/**
|
|
134
|
+
* The brain half of the report, or undefined when the config declared none.
|
|
135
|
+
*
|
|
136
|
+
* Exported for the same reason it exists: `naive up` printed `applied: true` and
|
|
137
|
+
* said NOTHING about brains, so a config that declared two of them looked
|
|
138
|
+
* identical to a config that declared none. `--plan` showed them (it prints the
|
|
139
|
+
* whole plan); apply did not. A provisioned thing that is not reported is the
|
|
140
|
+
* same silence as an unprovisioned one, one level up.
|
|
141
|
+
*
|
|
142
|
+
* 🔴 AND `unpersisted` IS PART OF THE REPORT, NOT AN EXTRA. The apply path
|
|
143
|
+
* computed the accept-and-drop list and this projection threw it away, so
|
|
144
|
+
* `naive up --plan` told the user which declared brain keys have no column and
|
|
145
|
+
* plain `naive up` — the command people actually run — did not. That is the same
|
|
146
|
+
* defect one level down: `packages/api/src/services/deployment-brains.ts`'s own
|
|
147
|
+
* header calls accept-and-drop "the exact defect this file exists to remove", and
|
|
148
|
+
* a report only the dry run prints is a report most operators never see.
|
|
149
|
+
*/
|
|
150
|
+
export function brainReport(brains) {
|
|
151
|
+
if (!brains || !brains.declared_in)
|
|
152
|
+
return undefined;
|
|
153
|
+
const unpersisted = brains.unpersisted ?? {};
|
|
154
|
+
return {
|
|
155
|
+
declared_in: brains.declared_in,
|
|
156
|
+
created: (brains.created ?? []).map((b) => b.name),
|
|
157
|
+
converged: (brains.converged ?? []).map((b) => b.name),
|
|
158
|
+
default: brains.default?.name ?? null,
|
|
159
|
+
retained: brains.retained_undeclared ?? [],
|
|
160
|
+
...(Object.keys(unpersisted).length > 0 ? { unpersisted } : {}),
|
|
161
|
+
...(brains.notes?.length ? { notes: brains.notes } : {}),
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* The per-team accept-and-drop warning, one line per team that declared any.
|
|
166
|
+
*
|
|
167
|
+
* The data has been on the wire the whole time (`plan.teams[].unconsumed`,
|
|
168
|
+
* computed by `unconsumedTeamKeys` in packages/api) and `naive up` printed none
|
|
169
|
+
* of it — `team({ governance: {…} })` applied green and enforced nothing, with
|
|
170
|
+
* nothing anywhere for the author to notice. Printed on the PLAN and on the
|
|
171
|
+
* APPLY, because a report only the dry run shows is a report most operators
|
|
172
|
+
* never see.
|
|
173
|
+
*/
|
|
174
|
+
export function unconsumedWarnings(teams) {
|
|
175
|
+
return teams
|
|
176
|
+
.filter((t) => (t.unconsumed ?? []).length > 0)
|
|
177
|
+
.map((t) => `⚠ team "${t.name}" DECLARED AND UNREAD: ${t.unconsumed.join(", ")} — carried to the ` +
|
|
178
|
+
"control plane and consumed by NOTHING. Do not treat these as in force. " +
|
|
179
|
+
"`defineProject` refuses them at define time; remove them, or keep the config on " +
|
|
180
|
+
"`defineConfig` (lenient) until a consumer lands.");
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* ONE deprecation line when the config still uses the legacy ECS surface —
|
|
184
|
+
* `runtime.pool()` declarations, an agent-level `runtime: "<pool>"` string, or
|
|
185
|
+
* `systems:`. All three keep working indefinitely; the line names what was
|
|
186
|
+
* found and where new configs should author instead.
|
|
187
|
+
*/
|
|
188
|
+
export function legacyDeprecationNotice(config) {
|
|
189
|
+
const found = [];
|
|
190
|
+
const pools = Object.keys(config.runtime ?? {});
|
|
191
|
+
if (pools.length > 0)
|
|
192
|
+
found.push(`runtime.pool() declarations (${pools.join(", ")})`);
|
|
193
|
+
const pinned = Object.entries(config.agents ?? {})
|
|
194
|
+
.filter(([, a]) => typeof a?.runtime === "string")
|
|
195
|
+
.map(([name]) => name);
|
|
196
|
+
if (pinned.length > 0)
|
|
197
|
+
found.push(`agent-level runtime: "<pool>" strings (${pinned.join(", ")})`);
|
|
198
|
+
const systems = Object.keys(config.systems ?? {});
|
|
199
|
+
if (systems.length > 0)
|
|
200
|
+
found.push(`systems: (${systems.join(", ")})`);
|
|
201
|
+
if (found.length === 0)
|
|
202
|
+
return null;
|
|
203
|
+
return (`DEPRECATED surface in use — ${found.join("; ")}. These keep working indefinitely; new configs ` +
|
|
204
|
+
"author `defineProject` with `teams:` on `runtime.durable()` (or `runtime.hermes()` for the hosted lane).");
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* ONE rename line when the config declares `agents:`. Separate from the legacy
|
|
208
|
+
* line above on purpose: `runtime.pool()` / `systems:` are a deprecated
|
|
209
|
+
* SURFACE, while `agents:` is a deprecated SPELLING — the block compiles to
|
|
210
|
+
* byte-for-byte what `kits:` compiles to, so the honest notice is "rename",
|
|
211
|
+
* not "migrate". A notice, never a refusal, on both entrypoints.
|
|
212
|
+
*/
|
|
213
|
+
export function agentsRenameNotice(config) {
|
|
214
|
+
const names = Object.keys(config.agents ?? {});
|
|
215
|
+
if (names.length === 0)
|
|
216
|
+
return null;
|
|
217
|
+
return (`DEPRECATED spelling in use — \`agents:\` (${names.join(", ")}). It keeps working indefinitely ` +
|
|
218
|
+
"and compiles to exactly what `kits:` compiles to; rename the block to `kits:` (and `agent()` " +
|
|
219
|
+
"entries to `kit()`) — an account kit is instantiated per child project via " +
|
|
220
|
+
"`naive.forProject(p).forChild(id).provision(\"<kit>\")`.");
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* The operator credential the DURABLE RUNTIME issued, read from the ENVIRONMENT.
|
|
224
|
+
*
|
|
225
|
+
* Never from `naive.config.ts`, and that is the whole point rather than a style
|
|
226
|
+
* choice. The config is committed, so a credential in it is a credential in git —
|
|
227
|
+
* and, more importantly, the config is the one thing that must not by itself be
|
|
228
|
+
* able to change where a tenant runs. Pointing a tenant at the durable runtime
|
|
229
|
+
* overwrites its `sidecar_url` and `container_auth_token` and nulls its
|
|
230
|
+
* `instance_id`, none of which is stashed anywhere. Requiring a second, separate
|
|
231
|
+
* act means an edit alone can never do it.
|
|
232
|
+
*
|
|
233
|
+
* `NAIVE_DURABLE_CREDENTIAL_<TEAM>` names one team; `NAIVE_DURABLE_CREDENTIAL`
|
|
234
|
+
* covers them all. Neither is ever printed, and the API never echoes it back.
|
|
235
|
+
*/
|
|
236
|
+
export function durableCredentialEnvVar(team) {
|
|
237
|
+
return `NAIVE_DURABLE_CREDENTIAL_${team.toUpperCase().replace(/[^A-Z0-9]+/g, "_")}`;
|
|
238
|
+
}
|
|
239
|
+
function readDurableCredentials(teams) {
|
|
240
|
+
const out = {};
|
|
241
|
+
const all = process.env.NAIVE_DURABLE_CREDENTIAL;
|
|
242
|
+
if (all)
|
|
243
|
+
out["*"] = all;
|
|
244
|
+
for (const team of teams) {
|
|
245
|
+
const secret = process.env[durableCredentialEnvVar(team)];
|
|
246
|
+
if (secret)
|
|
247
|
+
out[team] = secret;
|
|
248
|
+
}
|
|
249
|
+
return Object.keys(out).length > 0 ? out : undefined;
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* The recovery for ONE refused team — a command that EXISTS, or none.
|
|
253
|
+
*
|
|
254
|
+
* 🔴 `naive runtime migrate` IS NOT A COMMAND, AND THIS USED TO PRINT IT.
|
|
255
|
+
* Every refusal here used to emit
|
|
256
|
+
* `naive runtime migrate --tenant … --to durable --credential-from <file>`.
|
|
257
|
+
* Measured on the built CLI: `naive runtime migrate --tenant <id> --to hermes`
|
|
258
|
+
* answers
|
|
259
|
+
* `{"code":"cli_outdated","message":"Unknown command 'runtime'. Your naive CLI
|
|
260
|
+
* is v0.12.4; this command may exist in a newer version."}` — and it exists in
|
|
261
|
+
* NO version: `packages/cli/src/index.ts` registers no `runtime` group at all.
|
|
262
|
+
* So an operator whose apply was refused was handed a command that tells them to
|
|
263
|
+
* upgrade, forever. A next_step that cannot be run is worse than none, because it
|
|
264
|
+
* ends the search.
|
|
265
|
+
*
|
|
266
|
+
* The two cases are genuinely different and are answered differently:
|
|
267
|
+
*
|
|
268
|
+
* · `runtime_credential_required` — there IS a procedure, it works today, and
|
|
269
|
+
* it is the one this command already implements: put the operator credential
|
|
270
|
+
* the durable runtime issued in the environment and re-run. Never in
|
|
271
|
+
* `naive.config.ts`, which is committed.
|
|
272
|
+
* · everything else (`runtime_migration_requires_explicit_opt_in`,
|
|
273
|
+
* `runtime_claim_in_flight`, `runtime_placement_lost_race`,
|
|
274
|
+
* `runtime_not_configured`, `runtime_state_unknown`) — there is NO self-serve
|
|
275
|
+
* command on this build. Moving an existing tenant between runtimes
|
|
276
|
+
* overwrites `sidecar_url` and `container_auth_token` and nulls `instance_id`
|
|
277
|
+
* with nothing stashed, so the control plane refuses it and no client can do
|
|
278
|
+
* it either. Say that, and print the server's own message, rather than
|
|
279
|
+
* inventing a command to carry the blame.
|
|
280
|
+
*
|
|
281
|
+
* The API writes the same vapour into `details.migrate_command`
|
|
282
|
+
* (packages/api/src/services/placement.ts MIGRATE_COMMAND). That string is not
|
|
283
|
+
* this package's to change and is reported as a follow-up; this function is why
|
|
284
|
+
* `naive up` no longer repeats it.
|
|
285
|
+
*/
|
|
286
|
+
export function refusalNextStep(t) {
|
|
287
|
+
if (t.placement?.code === "runtime_credential_required") {
|
|
288
|
+
return {
|
|
289
|
+
command: `${durableCredentialEnvVar(t.name)}=<operator-credential> naive up`,
|
|
290
|
+
description: `team "${t.name}": the durable runtime issued an operator credential for this tenant and it is not in ` +
|
|
291
|
+
`the environment. Export it (or NAIVE_DURABLE_CREDENTIAL for every team) and re-run. Never put it in naive.config.ts.`,
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
return null;
|
|
295
|
+
}
|
|
296
|
+
/** Team names declared in the config, without evaluating anything the API will re-derive. */
|
|
297
|
+
function declaredTeamNames(config) {
|
|
298
|
+
const teams = config.teams;
|
|
299
|
+
return teams && typeof teams === "object" ? Object.keys(teams) : [];
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Wrap the config only when there is something to carry alongside it.
|
|
303
|
+
*
|
|
304
|
+
* `readConfig` on the API takes `body.config ?? body`, so both shapes work — but
|
|
305
|
+
* a caller that supplies no credential sends the exact bytes it always sent.
|
|
306
|
+
*/
|
|
307
|
+
function applyBody(config, credentials) {
|
|
308
|
+
return credentials ? { config, runtime_credentials: credentials } : config;
|
|
309
|
+
}
|
|
95
310
|
export const upCmd = new Command("up")
|
|
96
311
|
.description("Plan + apply the infrastructure, runtime pools, agent profiles, and systems declared in naive.config.ts")
|
|
97
312
|
.option("--plan", "Preview the plan (diff) without applying")
|
|
@@ -108,9 +323,41 @@ export const upCmd = new Command("up")
|
|
|
108
323
|
});
|
|
109
324
|
return;
|
|
110
325
|
}
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
|
|
326
|
+
const load = opts.local ? null : await loadConfig(found);
|
|
327
|
+
/* ── THE CONFIG REFUSED ITSELF. THAT IS NOT A TOOLCHAIN PROBLEM. ──────────
|
|
328
|
+
*
|
|
329
|
+
* 🔴 THIS BRANCH IS THE POINT OF THE `ConfigLoad` UNION ABOVE. Everything a
|
|
330
|
+
* define-time refusal is for arrives here: `brain_no_partitions`,
|
|
331
|
+
* `legacy_multi_team`, `brain_partition_unknown`, `residency_widens_company`,
|
|
332
|
+
* `company_brain_and_brains`, `program_undeclared`,
|
|
333
|
+
* `workspace_allowlist_empty`, `residency_unsupported_on_runtime`. Every one
|
|
334
|
+
* of those messages is written to be read by the person who wrote the config,
|
|
335
|
+
* and every one of them used to be swallowed by `catch { return null }`,
|
|
336
|
+
* relabelled "install @usenaive-sdk/iac + jiti", printed under
|
|
337
|
+
* `success: true`, and exited 0.
|
|
338
|
+
*
|
|
339
|
+
* So it is surfaced VERBATIM and exits non-zero. No regex fallback: the
|
|
340
|
+
* fallback's whole job is to say something useful when the config could not be
|
|
341
|
+
* READ, and this config was read perfectly — it said no.
|
|
342
|
+
*/
|
|
343
|
+
if (load && !load.ok && load.reason === "invalid") {
|
|
344
|
+
agentError({
|
|
345
|
+
action: "up",
|
|
346
|
+
error: {
|
|
347
|
+
code: "invalid_config",
|
|
348
|
+
message: load.message,
|
|
349
|
+
hint: `${found} evaluated and refused itself. This is the config's own named refusal, not a missing ` +
|
|
350
|
+
"dependency — @usenaive-sdk/iac and jiti both resolved. Fix the declaration it names and re-run.",
|
|
351
|
+
},
|
|
352
|
+
recovery_steps: [
|
|
353
|
+
{ command: "naive up --plan", description: "Re-check the config without applying anything" },
|
|
354
|
+
],
|
|
355
|
+
});
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
// Fallback: the TOOLCHAIN is missing (or --local was asked for), so the config
|
|
359
|
+
// could not be evaluated at all — local, regex-only validation.
|
|
360
|
+
if (!load || !load.ok) {
|
|
114
361
|
const source = readFileSync(found, "utf8");
|
|
115
362
|
const projectMatch = /project\s*:\s*["'`]([^"'`]+)["'`]/.exec(source);
|
|
116
363
|
const project = projectMatch?.[1] ?? "(unknown)";
|
|
@@ -134,34 +381,135 @@ export const upCmd = new Command("up")
|
|
|
134
381
|
opts.local
|
|
135
382
|
? `Local validation only for "${project}".`
|
|
136
383
|
: `Validated ${found} locally, but could not evaluate it (install @usenaive-sdk/iac + jiti, or commit a naive.config.json).`,
|
|
384
|
+
// Only ever printed for a real resolution failure now — an invalid config
|
|
385
|
+
// took the branch above. Said out loud so the two are never conflated again.
|
|
386
|
+
...(load ? [`Module resolution failed: ${load.message}`] : []),
|
|
137
387
|
],
|
|
138
388
|
});
|
|
139
389
|
return;
|
|
140
390
|
}
|
|
391
|
+
const config = load.config;
|
|
141
392
|
const project = typeof config.project === "string" ? config.project : undefined;
|
|
142
393
|
if (!project) {
|
|
143
394
|
agentError({ action: "up", error: { code: "invalid_input", message: "naive.config.ts must export a `project` name" }, recovery_steps: [] });
|
|
144
395
|
return;
|
|
145
396
|
}
|
|
397
|
+
const credentials = readDurableCredentials(declaredTeamNames(config));
|
|
398
|
+
// The organization's OTHER projects, if the config declares any. Each is a
|
|
399
|
+
// separate deployment under its own project key — the control plane keys
|
|
400
|
+
// deployments, pools and systems by `(company_id, project)`, so applying two
|
|
401
|
+
// projects is two applies of the same shape, not a new one. A config with no
|
|
402
|
+
// `projects:` block produces an empty list and everything below is untouched.
|
|
403
|
+
const extraProjects = Object.entries(config.projects ?? {})
|
|
404
|
+
.map(([key, p]) => p?.slug ?? key)
|
|
405
|
+
.filter((slug) => slug !== project);
|
|
146
406
|
// 1. Plan (diff desired vs current).
|
|
147
|
-
const planResp = await apiRequest("PUT", `/v1/deployments/${encodeURIComponent(project)}`, config);
|
|
407
|
+
const planResp = await apiRequest("PUT", `/v1/deployments/${encodeURIComponent(project)}`, applyBody(config, credentials));
|
|
148
408
|
handleApiError("up", planResp);
|
|
149
409
|
const plan = planResp.data?.plan;
|
|
410
|
+
const teamPlan = plan?.teams?.plan ?? [];
|
|
150
411
|
if (opts.plan) {
|
|
412
|
+
// A plan REFUSAL is not a warning to scroll past — it is the reason this
|
|
413
|
+
// apply will not do what the config asks. Print it, and exit non-zero so a
|
|
414
|
+
// CI step that gates on `naive up --plan` actually gates.
|
|
415
|
+
const wouldRefuse = teamPlan.filter((t) => t.placement?.status === "refused");
|
|
416
|
+
const extraPlans = [];
|
|
417
|
+
for (const extra of extraProjects) {
|
|
418
|
+
const resp = await apiRequest("PUT", `/v1/deployments/${encodeURIComponent(extra)}`, applyBody(config, credentials));
|
|
419
|
+
handleApiError("up", resp);
|
|
420
|
+
extraPlans.push({ project: extra, plan: resp.data?.plan });
|
|
421
|
+
}
|
|
151
422
|
agentOutput({
|
|
152
423
|
action: "up",
|
|
153
|
-
result: {
|
|
424
|
+
result: {
|
|
425
|
+
config: found,
|
|
426
|
+
project,
|
|
427
|
+
plan,
|
|
428
|
+
...(extraPlans.length ? { projects: extraPlans } : {}),
|
|
429
|
+
applied: false,
|
|
430
|
+
},
|
|
154
431
|
next_steps: [{ command: "naive up", description: "Apply the plan" }],
|
|
155
|
-
hints: [
|
|
432
|
+
hints: [
|
|
433
|
+
"Plan only — nothing was provisioned.",
|
|
434
|
+
...unconsumedWarnings(teamPlan),
|
|
435
|
+
...(legacyDeprecationNotice(config) ? [legacyDeprecationNotice(config)] : []),
|
|
436
|
+
...(agentsRenameNotice(config) ? [agentsRenameNotice(config)] : []),
|
|
437
|
+
...(extraPlans.length
|
|
438
|
+
? [`Planned ${extraPlans.length + 1} projects: ${[project, ...extraProjects].join(", ")}.`]
|
|
439
|
+
: []),
|
|
440
|
+
...wouldRefuse.map((t) => `team "${t.name}" WOULD BE REFUSED (${t.placement?.code}): ${t.placement?.message}`),
|
|
441
|
+
],
|
|
156
442
|
});
|
|
443
|
+
if (wouldRefuse.length > 0)
|
|
444
|
+
process.exitCode = 1;
|
|
157
445
|
return;
|
|
158
446
|
}
|
|
159
|
-
// 2. Apply (provision infra, register pools/systems/
|
|
160
|
-
|
|
447
|
+
// 2. Apply (provision infra, register pools/systems/roles, materialize and
|
|
448
|
+
// place declared teams).
|
|
449
|
+
const applyResp = await apiRequest("POST", `/v1/deployments/${encodeURIComponent(project)}/apply`, applyBody(config, credentials));
|
|
161
450
|
handleApiError("up", applyResp);
|
|
162
451
|
const data = applyResp.data;
|
|
163
452
|
const deployment = data.deployment;
|
|
164
453
|
const needsAction = (deployment?.resources ?? []).filter((r) => r.status === "needs_action" || r.status === "failed");
|
|
454
|
+
// The other declared projects, applied the same way and reported by name.
|
|
455
|
+
// Sequential on purpose: two applies of one organization touch the same
|
|
456
|
+
// brains and the same business programs, which the control plane serializes
|
|
457
|
+
// per company anyway.
|
|
458
|
+
const extraResults = [];
|
|
459
|
+
for (const extra of extraProjects) {
|
|
460
|
+
const resp = await apiRequest("POST", `/v1/deployments/${encodeURIComponent(extra)}/apply`, applyBody(config, credentials));
|
|
461
|
+
handleApiError("up", resp);
|
|
462
|
+
const extraData = resp.data;
|
|
463
|
+
extraResults.push({
|
|
464
|
+
project: extra,
|
|
465
|
+
status: extraData.deployment?.status,
|
|
466
|
+
resources: extraData.deployment?.resources,
|
|
467
|
+
});
|
|
468
|
+
}
|
|
469
|
+
// ── WHAT WAS PROVISIONED, AND WHAT WAS REFUSED AND WHY ────────────────────
|
|
470
|
+
// A declared team that produced nothing used to be indistinguishable from one
|
|
471
|
+
// that worked: `naive up` printed `applied: true` and moved on. Every declared
|
|
472
|
+
// team now appears here with the agents it materialized, and a durable team
|
|
473
|
+
// additionally with its placement — placed, converged, or refused with the
|
|
474
|
+
// reason. A refusal exits NON-ZERO: the apply did real work (infra, pools,
|
|
475
|
+
// hermes teams) and reporting that honestly means not also claiming the part
|
|
476
|
+
// that was refused succeeded.
|
|
477
|
+
const durableByTeam = new Map((data.teams ?? []).map((t) => [t.team, t]));
|
|
478
|
+
const hermesByTeam = new Map((data.staffing ?? []).map((s) => [s.system, s]));
|
|
479
|
+
const teams = teamPlan.map((t) => {
|
|
480
|
+
const durable = durableByTeam.get(t.name);
|
|
481
|
+
const hermes = hermesByTeam.get(t.name);
|
|
482
|
+
// BOTH LANES, not the first one that answered. A MIXED team — a hermes lead
|
|
483
|
+
// with a member pinned to `runtime.durable` — is reported by BOTH
|
|
484
|
+
// materializers, and `durable ?? hermes` printed one half and silently
|
|
485
|
+
// dropped the other, so the agents an apply created did not add up to the
|
|
486
|
+
// agents it reported. Each role is tagged with the runtime it landed on when
|
|
487
|
+
// the team has two, so "which lane is this agent in" is answerable from the
|
|
488
|
+
// output rather than by reading the config back.
|
|
489
|
+
const lanes = t.role_runtimes;
|
|
490
|
+
const label = (role, status) => lanes?.[role] ? `${role} (${status}, ${lanes[role]})` : `${role} (${status})`;
|
|
491
|
+
const agents = [...(hermes?.agents ?? []), ...(durable?.agents ?? [])].map((a) => label(a.role, a.provision_status));
|
|
492
|
+
return {
|
|
493
|
+
name: t.name,
|
|
494
|
+
runtime: t.runtime,
|
|
495
|
+
lead: t.lead_role,
|
|
496
|
+
...(lanes ? { role_runtimes: lanes } : {}),
|
|
497
|
+
...(durable?.subject ? { subject: durable.subject } : {}),
|
|
498
|
+
agents,
|
|
499
|
+
archived: [...(hermes?.archived ?? []), ...(durable?.archived ?? [])],
|
|
500
|
+
...(durable?.placement
|
|
501
|
+
? {
|
|
502
|
+
placement: {
|
|
503
|
+
status: durable.placement.status,
|
|
504
|
+
...(durable.placement.code ? { code: durable.placement.code } : {}),
|
|
505
|
+
...(durable.placement.message ? { reason: durable.placement.message } : {}),
|
|
506
|
+
},
|
|
507
|
+
}
|
|
508
|
+
: {}),
|
|
509
|
+
};
|
|
510
|
+
});
|
|
511
|
+
const refusedTeams = teams.filter((t) => t.placement?.status === "refused");
|
|
512
|
+
const brains = brainReport(data.brains);
|
|
165
513
|
// Deploy cloud.web source to the provisioned web project(s) (Vercel builds + hosts).
|
|
166
514
|
const webDeploys = {};
|
|
167
515
|
if (opts.deploy !== false) {
|
|
@@ -199,20 +547,71 @@ export const upCmd = new Command("up")
|
|
|
199
547
|
resources: deployment?.resources,
|
|
200
548
|
runtime_pools: Object.keys(config.runtime ?? {}),
|
|
201
549
|
systems: Object.keys(config.systems ?? {}),
|
|
550
|
+
...(brains ? { brains } : {}),
|
|
202
551
|
agents: Object.keys(config.agents ?? config.agentProfiles ?? config.templates ?? {}),
|
|
552
|
+
teams,
|
|
203
553
|
outputs: deployment?.outputs ?? {},
|
|
204
554
|
web_deployments: webDeploys,
|
|
555
|
+
...(extraResults.length ? { projects: extraResults } : {}),
|
|
205
556
|
applied: true,
|
|
206
557
|
},
|
|
207
558
|
next_steps: [
|
|
208
559
|
...needsAction.map((r) => ({ command: `naive status`, description: `${r.name}: ${r.requirement ?? r.status}` })),
|
|
560
|
+
...refusedTeams.map(refusalNextStep).filter((s) => s !== null),
|
|
209
561
|
{ command: `naive agent-profiles provision <name> --user <tenant>`, description: "Provision an agent profile for a tenant" },
|
|
210
562
|
],
|
|
211
563
|
hints: [
|
|
212
564
|
`Deployment "${project}" is ${deployment?.status ?? "?"}.`,
|
|
565
|
+
...unconsumedWarnings(teamPlan),
|
|
566
|
+
...(legacyDeprecationNotice(config) ? [legacyDeprecationNotice(config)] : []),
|
|
567
|
+
...(agentsRenameNotice(config) ? [agentsRenameNotice(config)] : []),
|
|
568
|
+
...extraResults.map((r) => `Deployment "${r.project}" is ${r.status ?? "?"}.`),
|
|
213
569
|
needsAction.length ? `${needsAction.length} resource(s) need action — see next_steps.` : "All declared infrastructure is ready.",
|
|
570
|
+
...(brains
|
|
571
|
+
? [
|
|
572
|
+
`brains (${brains.declared_in}): ${brains.created.length} created` +
|
|
573
|
+
`${brains.created.length ? ` (${brains.created.join(", ")})` : ""}, ` +
|
|
574
|
+
`${brains.converged.length} already present` +
|
|
575
|
+
`${brains.converged.length ? ` (${brains.converged.join(", ")})` : ""}` +
|
|
576
|
+
`${brains.default ? `; default is "${brains.default}"` : ""}` +
|
|
577
|
+
`${brains.retained.length ? `. RETAINED, not declared by this config: ${brains.retained.join(", ")} — remove one deliberately with \`naive brain rm\`.` : "."}`,
|
|
578
|
+
// The accept-and-drop list, on the APPLY path and not only in --plan.
|
|
579
|
+
...(brains.unpersisted
|
|
580
|
+
? [
|
|
581
|
+
"DECLARED BUT NOT STORED — " +
|
|
582
|
+
Object.entries(brains.unpersisted)
|
|
583
|
+
.map(([name, keys]) => `${name}: ${keys.join(", ")}`)
|
|
584
|
+
.join("; ") +
|
|
585
|
+
". `brain_knowledge_bases` has no column for these, so they were accepted and dropped. " +
|
|
586
|
+
"Nothing reads them; do not treat them as in force.",
|
|
587
|
+
]
|
|
588
|
+
: []),
|
|
589
|
+
...(brains.notes ?? []),
|
|
590
|
+
]
|
|
591
|
+
: []),
|
|
592
|
+
...teams
|
|
593
|
+
.filter((t) => t.placement?.status === "placed" || t.placement?.status === "converged")
|
|
594
|
+
.map((t) => t.placement?.status === "placed"
|
|
595
|
+
? `team "${t.name}" PLACED on the durable runtime (${t.agents.length} agent(s)). Its jurisdiction is fixed permanently by the first control request.`
|
|
596
|
+
: `team "${t.name}" was already on the durable runtime (${t.agents.length} agent(s)); nothing was rewritten.`),
|
|
597
|
+
...refusedTeams.map((t) => `team "${t.name}" REFUSED (${t.placement?.code}): ${t.placement?.reason}`),
|
|
598
|
+
// Said once, not per team. `naive runtime migrate` does not exist in this
|
|
599
|
+
// CLI or any released one — see refusalNextStep — so a refusal with no
|
|
600
|
+
// next_step gets the reason it has none instead of a command that 404s.
|
|
601
|
+
...(refusedTeams.some((t) => refusalNextStep(t) === null)
|
|
602
|
+
? [
|
|
603
|
+
"No `naive` command moves an existing tenant between runtimes, on this or any released version. " +
|
|
604
|
+
"Placement rewrites sidecar_url and container_auth_token and nulls instance_id with nothing stashed, " +
|
|
605
|
+
"so the control plane refuses a config edit that would do it. Read the refusal above and, if the move " +
|
|
606
|
+
"is intended, take it to an operator with direct access — there is no self-serve path.",
|
|
607
|
+
]
|
|
608
|
+
: []),
|
|
214
609
|
],
|
|
215
610
|
});
|
|
611
|
+
// The apply did real work and said so; it must not ALSO report success for
|
|
612
|
+
// the teams it refused. Non-zero, so a CI pipeline stops here.
|
|
613
|
+
if (refusedTeams.length > 0)
|
|
614
|
+
process.exitCode = 1;
|
|
216
615
|
});
|
|
217
616
|
export const downCmd = new Command("down")
|
|
218
617
|
.description("Tear down the infrastructure provisioned for this project (naive.config.ts)")
|
|
@@ -232,6 +631,7 @@ export const downCmd = new Command("down")
|
|
|
232
631
|
// Without --yes: preview what would be destroyed (no side effects).
|
|
233
632
|
if (!opts.yes) {
|
|
234
633
|
const current = await apiRequest("GET", `/v1/deployments/${encodeURIComponent(project)}`);
|
|
634
|
+
handleApiError("down", current);
|
|
235
635
|
const d = current.data ?? {};
|
|
236
636
|
agentOutput({
|
|
237
637
|
action: "down",
|