@orchestraworks/cli 0.5.7 → 0.5.8
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 +1 -1
- package/dist/cli/core/auth.d.ts +11 -1
- package/dist/cli/core/auth.js +48 -4
- package/dist/cli/core/errors.js +4 -0
- package/dist/cli/core/platform-client.d.ts +3 -2
- package/dist/cli/firstRun.d.ts +3 -3
- package/dist/cli/firstRun.js +6 -4
- package/docs/REFERENCE.md +18 -6
- package/node_modules/@orchestraworks/client/package.json +1 -1
- package/package.json +4 -4
- package/skills/orchestra/SKILL.md +8 -3
- package/skills/orchestra-fdk/references/authoring.md +8 -6
- package/skills/orchestra-fdk/references/troubleshooting.md +2 -1
package/README.md
CHANGED
|
@@ -20,7 +20,7 @@ The full guide to every construct, secrets, connections and troubleshooting is [
|
|
|
20
20
|
```bash
|
|
21
21
|
npm install -g @orchestraworks/cli # the CLI, on your path as `orchestra`
|
|
22
22
|
npm install @orchestraworks/cli # the current release
|
|
23
|
-
npm install @orchestraworks/cli@0.5.
|
|
23
|
+
npm install @orchestraworks/cli@0.5.8 # this exact release
|
|
24
24
|
```
|
|
25
25
|
|
|
26
26
|
Then `import { Factory, Agent, … } from "@orchestraworks/cli"` normally — the package `exports` entry resolves
|
package/dist/cli/core/auth.d.ts
CHANGED
|
@@ -14,6 +14,8 @@ export declare const AUTH_ENV: {
|
|
|
14
14
|
/** Read after `baseUrl`: the platform writes this name into every machine. */
|
|
15
15
|
readonly machineBaseUrl: "PLATFORM_BASE_URL";
|
|
16
16
|
readonly token: "ORCHESTRA_API_TOKEN";
|
|
17
|
+
/** Set by the worker on each harness and hook process; the file holds a token it renews. */
|
|
18
|
+
readonly tokenFile: "ORCHESTRA_API_TOKEN_FILE";
|
|
17
19
|
readonly organizationId: "ORCHESTRA_ORGANIZATION_ID";
|
|
18
20
|
/** Presence is the declaration. */
|
|
19
21
|
readonly edgeInjected: "SFDK_AUTH_EDGE_INJECTED";
|
|
@@ -26,7 +28,7 @@ export declare function baseUrlFromEnv(env: NodeJS.ProcessEnv): {
|
|
|
26
28
|
readonly variable: string;
|
|
27
29
|
} | undefined;
|
|
28
30
|
/** Which tier of the chain supplied the authorization. */
|
|
29
|
-
export type AuthProvider = "env" | "profile" | "edge";
|
|
31
|
+
export type AuthProvider = "env" | "file" | "profile" | "edge";
|
|
30
32
|
/**
|
|
31
33
|
* The conversation the command runs in, or undefined when the variable is absent, empty or whitespace only.
|
|
32
34
|
* The only reader of the variable: auth resolution and `login` both call it.
|
|
@@ -36,6 +38,9 @@ export declare function sessionIdFrom(env: NodeJS.ProcessEnv): string | undefine
|
|
|
36
38
|
* `session` rides along under the PROFILE provider only: `token` is the bearer as of this resolution, and
|
|
37
39
|
* the session is what keeps a bearer valid over time. The env provider's token has nothing to refresh it
|
|
38
40
|
* with and the edge provider holds no credential at all, so for both the field's absence is structural.
|
|
41
|
+
*
|
|
42
|
+
* Under the FILE provider the worker renews the token in place, so `token` reads the file again before each
|
|
43
|
+
* request, and the token names its own organization, so no organization is resolved.
|
|
39
44
|
*/
|
|
40
45
|
export type AuthResolution = {
|
|
41
46
|
readonly provider: "env" | "profile";
|
|
@@ -44,6 +49,11 @@ export type AuthResolution = {
|
|
|
44
49
|
readonly organizationId?: string;
|
|
45
50
|
readonly session?: CredentialSession;
|
|
46
51
|
readonly sessionId?: string;
|
|
52
|
+
} | {
|
|
53
|
+
readonly provider: "file";
|
|
54
|
+
readonly token: () => string;
|
|
55
|
+
readonly baseUrl: string;
|
|
56
|
+
readonly sessionId?: string;
|
|
47
57
|
} | {
|
|
48
58
|
readonly provider: "edge";
|
|
49
59
|
readonly baseUrl: string;
|
package/dist/cli/core/auth.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
// Every command resolves credentials through ONE ordered chain, with strict presence-is-selection
|
|
2
2
|
// precedence: `ORCHESTRA_API_TOKEN` (one opaque slot holding ANY principal's bearer — the CLI never looks inside,
|
|
3
|
-
// IAM judges server-side), then the
|
|
4
|
-
//
|
|
5
|
-
//
|
|
3
|
+
// IAM judges server-side), then the agent token file the worker names in `ORCHESTRA_API_TOKEN_FILE` and renews
|
|
4
|
+
// while the agent is placed, then the `orchestra auth login` profile, then the sandbox flag the platform
|
|
5
|
+
// DECLARES and never infers (the request goes out BARE and the egress stamps the per-request agent
|
|
6
|
+
// authorization), then a refusal.
|
|
6
7
|
//
|
|
7
8
|
// The precedence is purely CLIENT-side. Inside a sandbox the egress stamp REPLACES whatever the request
|
|
8
9
|
// carried, so an exported token is sent, then overwritten, and the call executes as the agent.
|
|
@@ -13,6 +14,7 @@
|
|
|
13
14
|
// Resolution is ASYNC because the profile tier renews itself. The renewal lives in
|
|
14
15
|
// `core/credentialSession.ts`: this module decides WHICH credential a command uses, and the session owns
|
|
15
16
|
// what happens to it over time.
|
|
17
|
+
import { readFileSync } from "node:fs";
|
|
16
18
|
import { CredentialSession } from "./credentialSession.js";
|
|
17
19
|
import { DEFAULT_PROFILE_NAME, loadProfile as loadProfileFromDisk, } from "./profile.js";
|
|
18
20
|
/**
|
|
@@ -28,6 +30,8 @@ export const AUTH_ENV = {
|
|
|
28
30
|
/** Read after `baseUrl`: the platform writes this name into every machine. */
|
|
29
31
|
machineBaseUrl: "PLATFORM_BASE_URL",
|
|
30
32
|
token: "ORCHESTRA_API_TOKEN",
|
|
33
|
+
/** Set by the worker on each harness and hook process; the file holds a token it renews. */
|
|
34
|
+
tokenFile: "ORCHESTRA_API_TOKEN_FILE",
|
|
31
35
|
organizationId: "ORCHESTRA_ORGANIZATION_ID",
|
|
32
36
|
/** Presence is the declaration. */
|
|
33
37
|
edgeInjected: "SFDK_AUTH_EDGE_INJECTED",
|
|
@@ -80,6 +84,26 @@ export class MissingBaseUrlError extends Error {
|
|
|
80
84
|
this.name = "MissingBaseUrlError";
|
|
81
85
|
}
|
|
82
86
|
}
|
|
87
|
+
/** The trimmed content, or undefined when the file is missing, unreadable, empty or whitespace only. */
|
|
88
|
+
function tokenFromFile(path) {
|
|
89
|
+
let content;
|
|
90
|
+
try {
|
|
91
|
+
content = readFileSync(path, "utf8");
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
return undefined;
|
|
95
|
+
}
|
|
96
|
+
const token = content.trim();
|
|
97
|
+
return token === "" ? undefined : token;
|
|
98
|
+
}
|
|
99
|
+
/** A failed read mid-command keeps the last token read rather than sending the request bare. */
|
|
100
|
+
function fileTokenReader(path, first) {
|
|
101
|
+
let last = first;
|
|
102
|
+
return () => {
|
|
103
|
+
last = tokenFromFile(path) ?? last;
|
|
104
|
+
return last;
|
|
105
|
+
};
|
|
106
|
+
}
|
|
83
107
|
/** Loopback hosts reachable over plain HTTP — the local dev-stack contract. */
|
|
84
108
|
const LOOPBACK_HOSTS = new Set(["127.0.0.1", "localhost", "::1"]);
|
|
85
109
|
/**
|
|
@@ -132,12 +156,18 @@ export async function resolveAuthWithWarnings(env, flags, deps = {}) {
|
|
|
132
156
|
const profile = (deps.loadProfile ?? loadProfileFromDisk)(flags.profile);
|
|
133
157
|
const baseUrl = firstPresent(flags.baseUrl, baseUrlFromEnv(env)?.value, profile?.baseUrl);
|
|
134
158
|
const envToken = firstPresent(env[AUTH_ENV.token]);
|
|
159
|
+
const tokenFilePath = firstPresent(env[AUTH_ENV.tokenFile]);
|
|
160
|
+
const fileToken = envToken === undefined && tokenFilePath !== undefined ? tokenFromFile(tokenFilePath) : undefined;
|
|
135
161
|
const profileToken = firstPresent(profile?.token);
|
|
136
162
|
const edgeDeclared = firstPresent(env[AUTH_ENV.edgeInjected]) !== undefined;
|
|
137
163
|
// Read before the provider split: a person may export it by hand under any provider.
|
|
138
164
|
const sessionId = sessionIdFrom(env);
|
|
139
165
|
const withSessionId = sessionId !== undefined ? { sessionId } : {};
|
|
140
|
-
if (envToken === undefined && profileToken === undefined && !edgeDeclared) {
|
|
166
|
+
if (envToken === undefined && fileToken === undefined && profileToken === undefined && !edgeDeclared) {
|
|
167
|
+
if (tokenFilePath !== undefined) {
|
|
168
|
+
throw new MissingCredentialsError(`no auth provider resolved: ${AUTH_ENV.tokenFile} names ${tokenFilePath}, which the worker has not ` +
|
|
169
|
+
`written yet. The worker writes it once the agent is placed on this machine; try again shortly.`);
|
|
170
|
+
}
|
|
141
171
|
throw new MissingCredentialsError(`no auth provider resolved: export ${AUTH_ENV.token}, run \`orchestra auth login\`, or run inside a host ` +
|
|
142
172
|
`where the platform declares ${AUTH_ENV.edgeInjected}.`);
|
|
143
173
|
}
|
|
@@ -146,6 +176,17 @@ export async function resolveAuthWithWarnings(env, flags, deps = {}) {
|
|
|
146
176
|
}
|
|
147
177
|
// Refuse before ANY network access, for every provider — the bare edge send included.
|
|
148
178
|
assertSecretTransport(baseUrl);
|
|
179
|
+
if (fileToken !== undefined && tokenFilePath !== undefined) {
|
|
180
|
+
return {
|
|
181
|
+
auth: {
|
|
182
|
+
provider: "file",
|
|
183
|
+
token: fileTokenReader(tokenFilePath, fileToken),
|
|
184
|
+
baseUrl,
|
|
185
|
+
...withSessionId,
|
|
186
|
+
},
|
|
187
|
+
warnings: [],
|
|
188
|
+
};
|
|
189
|
+
}
|
|
149
190
|
if (envToken === undefined && profileToken === undefined) {
|
|
150
191
|
// The egress stamps the authorization and the stamped identity carries the org.
|
|
151
192
|
return { auth: { provider: "edge", baseUrl, ...withSessionId }, warnings: [] };
|
|
@@ -223,6 +264,9 @@ export function platformTargetFrom(auth) {
|
|
|
223
264
|
if (auth.provider === "edge") {
|
|
224
265
|
return { baseUrl: auth.baseUrl, ...sessionId };
|
|
225
266
|
}
|
|
267
|
+
if (auth.provider === "file") {
|
|
268
|
+
return { baseUrl: auth.baseUrl, token: auth.token, ...sessionId };
|
|
269
|
+
}
|
|
226
270
|
return {
|
|
227
271
|
baseUrl: auth.baseUrl,
|
|
228
272
|
token: auth.token,
|
package/dist/cli/core/errors.js
CHANGED
|
@@ -182,6 +182,10 @@ function guidance(status, api, ctx) {
|
|
|
182
182
|
case "env":
|
|
183
183
|
return (`The token in ${AUTH_ENV.token} is not accepted. Renew that token and re-export ` +
|
|
184
184
|
`${AUTH_ENV.token}; a stored login profile is not what this command used.`);
|
|
185
|
+
case "file":
|
|
186
|
+
return (`The agent token in the file ${AUTH_ENV.tokenFile} names is not accepted. The worker renews that ` +
|
|
187
|
+
`file, so there is nothing to renew here: the token expired before the worker renewed it, or the ` +
|
|
188
|
+
`agent's placement on this machine, the machine's state or the agent's status refused it.`);
|
|
185
189
|
case "edge":
|
|
186
190
|
return (`Authorization is stamped at the egress edge (${AUTH_ENV.edgeInjected} is declared), so there is ` +
|
|
187
191
|
`no credential here to renew — the stamping path itself failed. Check the egress/identity plane.`);
|
|
@@ -50,7 +50,8 @@ export declare function withRefreshRetry(fetchApi: FetchAPI, session: Credential
|
|
|
50
50
|
*/
|
|
51
51
|
export interface PlatformTarget {
|
|
52
52
|
readonly baseUrl: string;
|
|
53
|
-
|
|
53
|
+
/** The bearer, or under the file provider a reader of the worker's renewed token, called before each request. */
|
|
54
|
+
readonly token?: string | (() => string);
|
|
54
55
|
readonly organizationId?: string;
|
|
55
56
|
/**
|
|
56
57
|
* `token` above is the bearer as of auth resolution; the session is what keeps a bearer valid, and every
|
|
@@ -73,7 +74,7 @@ export declare function createPlatformApis(target: PlatformTarget, fetchApi: Fet
|
|
|
73
74
|
export interface RawPlatformRequest {
|
|
74
75
|
baseUrl: string;
|
|
75
76
|
/** Absent for the bare edge send, exactly as for a typed command. */
|
|
76
|
-
token?: string;
|
|
77
|
+
token?: string | (() => string);
|
|
77
78
|
organizationId?: string;
|
|
78
79
|
/** The live credentials behind `token`, when the resolution carried one. */
|
|
79
80
|
session?: CredentialSession;
|
package/dist/cli/firstRun.d.ts
CHANGED
|
@@ -56,9 +56,9 @@ export interface FirstRunGateDeps extends InterviewDeps {
|
|
|
56
56
|
}
|
|
57
57
|
/**
|
|
58
58
|
* The pre-command gate: the interview runs on a machine where a person is present and nothing is
|
|
59
|
-
* configured. A credential supplied by the environment — an exported token,
|
|
60
|
-
*
|
|
61
|
-
* command's own rules are untouched either way.
|
|
59
|
+
* configured. A credential supplied by the environment — an exported token, the token file the worker
|
|
60
|
+
* names, written yet or not, or the marker the platform sets on a machine where the egress stamps the
|
|
61
|
+
* authorization — skips the interview and nothing else, so a command's own rules are untouched either way.
|
|
62
62
|
*
|
|
63
63
|
* The terminal is checked first, so a run with no person to ask touches neither the disk nor the network.
|
|
64
64
|
*/
|
package/dist/cli/firstRun.js
CHANGED
|
@@ -168,9 +168,9 @@ async function resolveHarnesses(flags, context) {
|
|
|
168
168
|
}
|
|
169
169
|
/**
|
|
170
170
|
* The pre-command gate: the interview runs on a machine where a person is present and nothing is
|
|
171
|
-
* configured. A credential supplied by the environment — an exported token,
|
|
172
|
-
*
|
|
173
|
-
* command's own rules are untouched either way.
|
|
171
|
+
* configured. A credential supplied by the environment — an exported token, the token file the worker
|
|
172
|
+
* names, written yet or not, or the marker the platform sets on a machine where the egress stamps the
|
|
173
|
+
* authorization — skips the interview and nothing else, so a command's own rules are untouched either way.
|
|
174
174
|
*
|
|
175
175
|
* The terminal is checked first, so a run with no person to ask touches neither the disk nor the network.
|
|
176
176
|
*/
|
|
@@ -179,7 +179,9 @@ export async function runFirstRunIfNeeded(flags, deps = {}) {
|
|
|
179
179
|
if ((deps.isTTY ?? process.stdin.isTTY === true) !== true) {
|
|
180
180
|
return;
|
|
181
181
|
}
|
|
182
|
-
if (firstPresent(env[AUTH_ENV.token]) !== undefined ||
|
|
182
|
+
if (firstPresent(env[AUTH_ENV.token]) !== undefined ||
|
|
183
|
+
firstPresent(env[AUTH_ENV.tokenFile]) !== undefined ||
|
|
184
|
+
firstPresent(env[AUTH_ENV.edgeInjected]) !== undefined) {
|
|
183
185
|
return;
|
|
184
186
|
}
|
|
185
187
|
const loadProfile = deps.loadProfile ?? loadProfileFromDisk;
|
package/docs/REFERENCE.md
CHANGED
|
@@ -132,16 +132,28 @@ export ORCHESTRA_ORGANIZATION_ID="org_…"
|
|
|
132
132
|
```
|
|
133
133
|
|
|
134
134
|
**The auth-provider chain** (one ordered chain for every command, presence is selection):
|
|
135
|
-
`ORCHESTRA_API_TOKEN` → the `
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
135
|
+
`ORCHESTRA_API_TOKEN` → the file `ORCHESTRA_API_TOKEN_FILE` names → the `login` profile → the
|
|
136
|
+
platform-injected machine edge flag (`SFDK_AUTH_EDGE_INJECTED`) → a named missing-credentials error.
|
|
137
|
+
There is **no `--token` flag**: a token never appears in argv.
|
|
138
|
+
|
|
139
|
+
- **The token file.** On an Orchestra machine, the worker writes the agent's own platform token to a
|
|
140
|
+
file and renews it before it expires. It sets `ORCHESTRA_API_TOKEN_FILE` to that file's path on each
|
|
141
|
+
agent process. The CLI reads the file again before each request, so a long `orchestra sessions wait`
|
|
142
|
+
always sends the current token. A missing, empty or unreadable file counts as absent. The worker sets
|
|
143
|
+
the path before it writes the file, so right after the agent starts, a command may report that the
|
|
144
|
+
file is not written yet.
|
|
145
|
+
- **The edge flag.** On a machine behind the platform's network edge, the edge adds the agent's
|
|
146
|
+
authorization to each call to the platform. It replaces any token the request carried with a token for
|
|
147
|
+
the same agent. With the edge flag and no token, the CLI sends the request **bare**.
|
|
148
|
+
- **A 401 under the token file.** The agent has nothing to renew. The token expired before the worker
|
|
149
|
+
renewed it, or the agent is no longer placed on the machine, the machine stopped, or the agent is not
|
|
150
|
+
active.
|
|
139
151
|
|
|
140
152
|
**Resolved alongside the chain, per field:** the base URL (`--base-url` → `ORCHESTRA_BASE_URL`, then `PLATFORM_BASE_URL` → profile;
|
|
141
153
|
`login` alone adds the built-in default as a fourth tier, because it is the command that has no profile
|
|
142
154
|
to read yet — every other command needs one of the three, and says so if it has none)
|
|
143
|
-
and the organization (`--org` → `ORCHESTRA_ORGANIZATION_ID` → profile; under the
|
|
144
|
-
rides the
|
|
155
|
+
and the organization (`--org` → `ORCHESTRA_ORGANIZATION_ID` → profile; under the token file and the edge flag
|
|
156
|
+
the organization rides the agent's token server-side). Every authenticated request requires HTTPS or loopback — the
|
|
145
157
|
bearer is itself a credential — with the unauthenticated `health` ping the one exemption. `diff`/`apply`
|
|
146
158
|
print the resolved target org as a banner before any read/write, and warn when an exported
|
|
147
159
|
`ORCHESTRA_ORGANIZATION_ID` diverges from the profile's org (so a stale export can't silently redirect writes).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@orchestraworks/cli",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.8",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "The Orchestra command line.",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -109,13 +109,13 @@
|
|
|
109
109
|
},
|
|
110
110
|
"dependencies": {
|
|
111
111
|
"@agentclientprotocol/sdk": "1.3.0",
|
|
112
|
-
"@orchestraworks/client": "0.5.
|
|
113
|
-
"@orchestraworks/worker": "0.0.
|
|
112
|
+
"@orchestraworks/client": "0.5.8",
|
|
113
|
+
"@orchestraworks/worker": "0.0.141",
|
|
114
114
|
"commander": "^14.0.0",
|
|
115
115
|
"liquidjs": "^10.28.0"
|
|
116
116
|
},
|
|
117
117
|
"optionalDependencies": {
|
|
118
|
-
"@orchestraworks/local": "0.0.
|
|
118
|
+
"@orchestraworks/local": "0.0.141"
|
|
119
119
|
},
|
|
120
120
|
"bundleDependencies": [
|
|
121
121
|
"@orchestraworks/client"
|
|
@@ -549,7 +549,8 @@ names its team, and a row with none sits in Shared.
|
|
|
549
549
|
|
|
550
550
|
<!-- Sources, on the kit's own branch: cli/src/cli/main.ts:60-160 (two jobs; nouns match API paths; standard
|
|
551
551
|
verbs; --output json; --body @file.json; approval defaults; orchestra api), cli/src/cli/core/auth.ts:1-45
|
|
552
|
-
(ORCHESTRA_API_TOKEN, then the login profile, then the machine's
|
|
552
|
+
(ORCHESTRA_API_TOKEN, then the file ORCHESTRA_API_TOKEN_FILE names, then the login profile, then the machine's
|
|
553
|
+
edge; ORCHESTRA_BASE_URL),
|
|
553
554
|
cli/src/cli/core/profile.ts:39-52 (profiles in ~/.config/orchestra/config.json), cli/src/generated/cli-commands.json
|
|
554
555
|
(each command's answer), cli/src/cli/core/output.ts:94-113 and :143-145 (json prints the answer as it is),
|
|
555
556
|
cli/src/cli/core/errors.ts:287-296 and :366-370 (the json error, on stderr), cli/src/cli/core/login.ts:361-363 (a
|
|
@@ -581,8 +582,12 @@ each part at length. What follows is only what a reader needs to run a command s
|
|
|
581
582
|
`~/.config/orchestra/config.json`. It selects the person's organization when they have one; with several,
|
|
582
583
|
name it: `orchestra auth login --org <organization>`. The browser sign-in renews itself. A token typed at the
|
|
583
584
|
`--no-browser` prompt cannot renew itself, so it suits a short run only.
|
|
584
|
-
- **Scripts.** `ORCHESTRA_API_TOKEN` and `ORCHESTRA_BASE_URL` in the shell are used before the profile.
|
|
585
|
-
|
|
585
|
+
- **Scripts.** `ORCHESTRA_API_TOKEN` and `ORCHESTRA_BASE_URL` in the shell are used before the profile.
|
|
586
|
+
- **On an Orchestra machine.** The worker writes the agent's own token to a file, renews it, and names the
|
|
587
|
+
file in `ORCHESTRA_API_TOKEN_FILE`. `orchestra` reads that file before each request, so the agent needs no
|
|
588
|
+
sign-in. Where a network edge stands in front of the machine, the edge replaces that token with one for the
|
|
589
|
+
same agent. A 401 here is not fixed by `orchestra auth login`: the token expired before the worker renewed
|
|
590
|
+
it, or the agent is no longer placed on the machine.
|
|
586
591
|
- **Fallback.** `orchestra api <METHOD> </path>` makes a raw call when a typed command is missing.
|
|
587
592
|
|
|
588
593
|
A first look at an organization, in the order a new reader runs it:
|
|
@@ -1106,17 +1106,19 @@ itself a credential.
|
|
|
1106
1106
|
An agent running inside a machine types the **same commands** as an operator at a workstation. There
|
|
1107
1107
|
is no separate agent CLI, no agent-only spelling, and nothing for you to author differently:
|
|
1108
1108
|
|
|
1109
|
-
- **The agent
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
`
|
|
1113
|
-
|
|
1109
|
+
- **The agent sets no token.** The worker writes the agent's own short-lived platform token to a file,
|
|
1110
|
+
renews it before it expires, and sets `ORCHESTRA_API_TOKEN_FILE` to that file's path on each agent
|
|
1111
|
+
process. The CLI reads the file before each request and sends its token to the platform's public HTTPS
|
|
1112
|
+
name (`PLATFORM_BASE_URL`), with no organization header, because the token names its organization.
|
|
1113
|
+
The token works only while the agent is placed on that machine. A token set in `ORCHESTRA_API_TOKEN`
|
|
1114
|
+
comes before the file. On a machine behind the platform's network edge, the edge replaces the
|
|
1115
|
+
`Authorization` header of each call with a token for the same agent, whatever token the call carried.
|
|
1114
1116
|
- **IAM is the only difference between principals.** The same command run by an agent and by a human
|
|
1115
1117
|
differs only in what their grants allow; a refusal is a permission error naming the action, never
|
|
1116
1118
|
an instruction to log in (there is nothing in a machine to log into).
|
|
1117
1119
|
- **The factory loop works there too.** `synth`, `diff` and `apply` (and `grant`) are ordinary
|
|
1118
1120
|
platform callers, so an agent can preview and apply a factory from inside its machine with no token
|
|
1119
|
-
and no organization set — the same
|
|
1121
|
+
and no organization set — the same token file, judged by the same IAM.
|
|
1120
1122
|
- **Triggers are runtime wiring: agents observe it, they do not author it.** Declare them in code
|
|
1121
1123
|
(`new Trigger(factory, …)`) or have an explicitly granted principal create them. The managed
|
|
1122
1124
|
worker role deliberately carries **no** trigger-authoring capability, so `triggers create`
|
|
@@ -48,7 +48,8 @@ The network edge answers the agent, not you. The agent sees one of these:
|
|
|
48
48
|
host. `details.reason` is `policy_denied` or `ambiguous_match` (two rules match with equal weight). The
|
|
49
49
|
answer does not name a rule.
|
|
50
50
|
- `403 egress identity missing` or `egress destination mismatch`, or `502 egress broker unavailable` or
|
|
51
|
-
`egress upstream unavailable`: a fault on the path, not the rules.
|
|
51
|
+
`egress upstream unavailable`: a fault on the path, not the rules. A `421 egress destination mismatch`
|
|
52
|
+
asks the client to send the request again on a new connection, and a browser does this by itself.
|
|
52
53
|
|
|
53
54
|
To check the rules the machine has now, read `egressPolicy` in `get-diagnostics`: `defaultAction`, and each
|
|
54
55
|
rule's name, priority, action, hosts and paths. Fix the rules in code and apply. Rules reach running
|