primitive-admin 1.2.0-alpha.7 → 1.2.0-alpha.9

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.
Files changed (93) hide show
  1. package/README.md +8 -2
  2. package/assets/skill/skills/primitive-platform/SKILL.md +22 -8
  3. package/dist/src/commands/apps-children.d.ts +1 -1
  4. package/dist/src/commands/apps-children.js +55 -145
  5. package/dist/src/commands/apps-children.js.map +1 -1
  6. package/dist/src/commands/auth.js +107 -33
  7. package/dist/src/commands/auth.js.map +1 -1
  8. package/dist/src/commands/database-types.js +13 -313
  9. package/dist/src/commands/database-types.js.map +1 -1
  10. package/dist/src/commands/databases.js +13 -1
  11. package/dist/src/commands/databases.js.map +1 -1
  12. package/dist/src/commands/documents.js +70 -11
  13. package/dist/src/commands/documents.js.map +1 -1
  14. package/dist/src/commands/llm.js +9 -6
  15. package/dist/src/commands/llm.js.map +1 -1
  16. package/dist/src/commands/settings.d.ts +12 -9
  17. package/dist/src/commands/settings.js +31 -79
  18. package/dist/src/commands/settings.js.map +1 -1
  19. package/dist/src/commands/sync-app-settings.d.ts +12 -2
  20. package/dist/src/commands/sync-app-settings.js +35 -10
  21. package/dist/src/commands/sync-app-settings.js.map +1 -1
  22. package/dist/src/commands/sync.d.ts +1 -8
  23. package/dist/src/commands/sync.js +47 -45
  24. package/dist/src/commands/sync.js.map +1 -1
  25. package/dist/src/lib/api-client.d.ts +46 -6
  26. package/dist/src/lib/api-client.js +156 -64
  27. package/dist/src/lib/api-client.js.map +1 -1
  28. package/dist/src/lib/app-settings-descriptor.d.ts +38 -0
  29. package/dist/src/lib/app-settings-descriptor.js +96 -4
  30. package/dist/src/lib/app-settings-descriptor.js.map +1 -1
  31. package/dist/src/lib/child-apps-local.d.ts +0 -46
  32. package/dist/src/lib/child-apps-local.js +2 -72
  33. package/dist/src/lib/child-apps-local.js.map +1 -1
  34. package/dist/src/lib/credentials-home.d.ts +51 -0
  35. package/dist/src/lib/credentials-home.js +169 -0
  36. package/dist/src/lib/credentials-home.js.map +1 -0
  37. package/dist/src/lib/credentials-lock.d.ts +80 -0
  38. package/dist/src/lib/credentials-lock.js +222 -0
  39. package/dist/src/lib/credentials-lock.js.map +1 -0
  40. package/dist/src/lib/credentials-store.d.ts +56 -4
  41. package/dist/src/lib/credentials-store.js +236 -78
  42. package/dist/src/lib/credentials-store.js.map +1 -1
  43. package/dist/src/lib/env-resolver-core.d.ts +1 -1
  44. package/dist/src/lib/env-resolver-core.js.map +1 -1
  45. package/dist/src/lib/function-bundle.d.ts +38 -18
  46. package/dist/src/lib/function-bundle.js +60 -35
  47. package/dist/src/lib/function-bundle.js.map +1 -1
  48. package/dist/src/lib/function-document-types.d.ts +5 -3
  49. package/dist/src/lib/function-document-types.js +10 -14
  50. package/dist/src/lib/function-document-types.js.map +1 -1
  51. package/dist/src/lib/function-source-roots.d.ts +67 -0
  52. package/dist/src/lib/function-source-roots.js +148 -0
  53. package/dist/src/lib/function-source-roots.js.map +1 -0
  54. package/dist/src/lib/function-sync.d.ts +27 -0
  55. package/dist/src/lib/function-sync.js +16 -3
  56. package/dist/src/lib/function-sync.js.map +1 -1
  57. package/dist/src/lib/function-triggers.d.ts +15 -3
  58. package/dist/src/lib/function-triggers.js +72 -10
  59. package/dist/src/lib/function-triggers.js.map +1 -1
  60. package/dist/src/lib/function-typecheck.d.ts +37 -2
  61. package/dist/src/lib/function-typecheck.js +210 -28
  62. package/dist/src/lib/function-typecheck.js.map +1 -1
  63. package/dist/src/lib/generated-config-surfaces.d.ts +48 -11
  64. package/dist/src/lib/generated-config-surfaces.js +178 -49
  65. package/dist/src/lib/generated-config-surfaces.js.map +1 -1
  66. package/dist/src/lib/generated-sdk-types.d.ts +1 -1
  67. package/dist/src/lib/generated-sdk-types.js +1 -1
  68. package/dist/src/lib/generated-sdk-types.js.map +1 -1
  69. package/dist/src/lib/generated-workflow-model-fields.d.ts +20 -0
  70. package/dist/src/lib/generated-workflow-model-fields.js +53 -0
  71. package/dist/src/lib/generated-workflow-model-fields.js.map +1 -0
  72. package/dist/src/lib/login-scope-lines.d.ts +1 -1
  73. package/dist/src/lib/login-scope-lines.js +1 -1
  74. package/dist/src/lib/login-scope-lines.js.map +1 -1
  75. package/dist/src/lib/project-config.d.ts +7 -0
  76. package/dist/src/lib/project-config.js +22 -1
  77. package/dist/src/lib/project-config.js.map +1 -1
  78. package/dist/src/lib/prompt-schema-codegen.d.ts +10 -3
  79. package/dist/src/lib/prompt-schema-codegen.js +52 -14
  80. package/dist/src/lib/prompt-schema-codegen.js.map +1 -1
  81. package/dist/src/lib/root-import-decision.d.ts +9 -4
  82. package/dist/src/lib/root-import-decision.js +24 -3
  83. package/dist/src/lib/root-import-decision.js.map +1 -1
  84. package/dist/src/lib/scope-request.d.ts +8 -5
  85. package/dist/src/lib/scope-request.js +17 -8
  86. package/dist/src/lib/scope-request.js.map +1 -1
  87. package/dist/src/lib/swift-codegen/agentGenerator.d.ts +6 -2
  88. package/dist/src/lib/swift-codegen/agentGenerator.js +56 -4
  89. package/dist/src/lib/swift-codegen/agentGenerator.js.map +1 -1
  90. package/dist/src/lib/workflow-field-descriptor.d.ts +91 -0
  91. package/dist/src/lib/workflow-field-descriptor.js +153 -0
  92. package/dist/src/lib/workflow-field-descriptor.js.map +1 -0
  93. package/package.json +2 -2
@@ -0,0 +1,80 @@
1
+ /**
2
+ * The cross-process lock and atomic replace every project-mode credentials
3
+ * write goes through (#4492).
4
+ *
5
+ * Several CLI processes write one credentials file — one agent per git
6
+ * worktree, all sharing the main worktree's login — so a write is a locked
7
+ * read-modify-write, and the file is replaced by a rename, never rewritten in
8
+ * place: a reader never sees a partial file.
9
+ *
10
+ * The lock is `credentials.json.lock`, created exclusively and holding its
11
+ * owner's token (`<pid>:<random>`). A lock older than {@link LOCK_STALE_MS}
12
+ * was left by a crashed process; a contender reclaims it by renaming it to a
13
+ * name of its own, which exactly one contender can do, and removes it only if
14
+ * it still holds the token it read — otherwise a live lock was moved, and it
15
+ * is put back unless another writer took the lock meanwhile. A holder proves,
16
+ * immediately before it commits, that it still owns the lock and that the file
17
+ * is still the version it read — a holder whose lock was moved away and put
18
+ * back may have missed another writer's commit in between — and releases only
19
+ * its own lock.
20
+ *
21
+ * Node has no advisory file lock, so one window remains: between a holder's
22
+ * checks and its rename, a contender that just misreclaimed that lock could
23
+ * let a third writer in. Every other interleaving ends in a failed write,
24
+ * never a lost update.
25
+ */
26
+ /** A lock untouched for this long was left by a crashed process. */
27
+ export declare const LOCK_STALE_MS = 10000;
28
+ /** How long a writer waits for a held lock before it fails. */
29
+ export declare const LOCK_WAIT_MS = 5000;
30
+ /** The lock on a credentials directory, held by this process. */
31
+ export interface CredentialsLock {
32
+ /** The lock file. */
33
+ readonly path: string;
34
+ /** This holder's token, the lock file's content while it holds it. */
35
+ readonly token: string;
36
+ /** Whether the lock file still holds this holder's token. */
37
+ ownsLock(): boolean;
38
+ /** Remove the lock if, and only if, this holder still owns it. Never throws. */
39
+ release(): void;
40
+ }
41
+ /** A write that could not take, or lost, the lock on a credentials file. */
42
+ export declare class CredentialsLockedError extends Error {
43
+ constructor(lockPath: string);
44
+ }
45
+ /**
46
+ * Reclaim the stale lock at `lockPath`, whose content was `seen` when it was
47
+ * judged stale: rename it to a name of `token`'s own, then remove it if it
48
+ * still holds `seen`, or put it back if it does not (a live writer took the
49
+ * lock in between). Gone already is not an error: another contender won.
50
+ */
51
+ export declare function reclaimStaleLock(lockPath: string, token: string, seen: string): void;
52
+ /**
53
+ * Put a live lock that a reclaimer moved to `moved` back at `lockPath` — but
54
+ * only if no other writer has taken the lock since, which a rename would
55
+ * silently replace. When one has, the displaced holder has lost the lock and
56
+ * fails its ownership check. The caller removes `moved` either way.
57
+ */
58
+ export declare function restoreDisplacedLock(moved: string, lockPath: string): void;
59
+ /**
60
+ * The version of the file at `path` as a writer read it: what an atomic
61
+ * replace changes (the inode) and what an in-place write changes (size and
62
+ * times). `"absent"` when there is no file.
63
+ */
64
+ export declare function fileVersion(path: string): string;
65
+ /**
66
+ * Take the lock on the credentials file in `dir`, waiting up to
67
+ * {@link LOCK_WAIT_MS} for another writer and reclaiming a stale lock. Throws
68
+ * {@link CredentialsLockedError} naming the lock when it stays held.
69
+ */
70
+ export declare function acquireCredentialsLock(dir: string): CredentialsLock;
71
+ /**
72
+ * Replace `path` with `content` while `lock` is held: write a private
73
+ * temporary file beside it, check the lock is still this holder's and, when
74
+ * `readVersion` is given, that `path` is still the {@link fileVersion} the
75
+ * holder read under the lock, then rename the temporary file over `path`. A
76
+ * holder that lost the lock, or whose read another writer's commit has
77
+ * overtaken, removes its temporary file and throws
78
+ * {@link CredentialsLockedError}, leaving `path` as it was.
79
+ */
80
+ export declare function replaceFileUnderLock(lock: CredentialsLock, path: string, content: string, readVersion?: string): void;
@@ -0,0 +1,222 @@
1
+ /**
2
+ * The cross-process lock and atomic replace every project-mode credentials
3
+ * write goes through (#4492).
4
+ *
5
+ * Several CLI processes write one credentials file — one agent per git
6
+ * worktree, all sharing the main worktree's login — so a write is a locked
7
+ * read-modify-write, and the file is replaced by a rename, never rewritten in
8
+ * place: a reader never sees a partial file.
9
+ *
10
+ * The lock is `credentials.json.lock`, created exclusively and holding its
11
+ * owner's token (`<pid>:<random>`). A lock older than {@link LOCK_STALE_MS}
12
+ * was left by a crashed process; a contender reclaims it by renaming it to a
13
+ * name of its own, which exactly one contender can do, and removes it only if
14
+ * it still holds the token it read — otherwise a live lock was moved, and it
15
+ * is put back unless another writer took the lock meanwhile. A holder proves,
16
+ * immediately before it commits, that it still owns the lock and that the file
17
+ * is still the version it read — a holder whose lock was moved away and put
18
+ * back may have missed another writer's commit in between — and releases only
19
+ * its own lock.
20
+ *
21
+ * Node has no advisory file lock, so one window remains: between a holder's
22
+ * checks and its rename, a contender that just misreclaimed that lock could
23
+ * let a third writer in. Every other interleaving ends in a failed write,
24
+ * never a lost update.
25
+ */
26
+ import { randomBytes } from "crypto";
27
+ import { chmodSync, linkSync, readdirSync, readFileSync, renameSync, statSync, unlinkSync, writeFileSync, } from "fs";
28
+ import { basename, dirname, join } from "path";
29
+ /** A lock untouched for this long was left by a crashed process. */
30
+ export const LOCK_STALE_MS = 10_000;
31
+ /** How long a writer waits for a held lock before it fails. */
32
+ export const LOCK_WAIT_MS = 5_000;
33
+ const RETRY_MS = 25;
34
+ const LOCKED_FILE = "credentials.json";
35
+ /** A write that could not take, or lost, the lock on a credentials file. */
36
+ export class CredentialsLockedError extends Error {
37
+ constructor(lockPath) {
38
+ super(`credentials file is locked: ${lockPath}`);
39
+ this.name = "CredentialsLockedError";
40
+ }
41
+ }
42
+ function sleep(ms) {
43
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
44
+ }
45
+ const isMissing = (err) => err?.code === "ENOENT";
46
+ /**
47
+ * Reclaim the stale lock at `lockPath`, whose content was `seen` when it was
48
+ * judged stale: rename it to a name of `token`'s own, then remove it if it
49
+ * still holds `seen`, or put it back if it does not (a live writer took the
50
+ * lock in between). Gone already is not an error: another contender won.
51
+ */
52
+ export function reclaimStaleLock(lockPath, token, seen) {
53
+ const moved = `${lockPath}.stale.${token}`;
54
+ try {
55
+ renameSync(lockPath, moved);
56
+ }
57
+ catch (err) {
58
+ if (isMissing(err))
59
+ return;
60
+ throw err;
61
+ }
62
+ let content;
63
+ try {
64
+ content = readFileSync(moved, "utf-8");
65
+ }
66
+ catch {
67
+ return;
68
+ }
69
+ if (content !== seen)
70
+ restoreDisplacedLock(moved, lockPath);
71
+ try {
72
+ unlinkSync(moved);
73
+ }
74
+ catch {
75
+ // Swept by the next release.
76
+ }
77
+ }
78
+ /**
79
+ * Put a live lock that a reclaimer moved to `moved` back at `lockPath` — but
80
+ * only if no other writer has taken the lock since, which a rename would
81
+ * silently replace. When one has, the displaced holder has lost the lock and
82
+ * fails its ownership check. The caller removes `moved` either way.
83
+ */
84
+ export function restoreDisplacedLock(moved, lockPath) {
85
+ try {
86
+ linkSync(moved, lockPath);
87
+ }
88
+ catch {
89
+ // Taken by another writer, or no hard links here: the displaced holder
90
+ // has lost the lock.
91
+ }
92
+ }
93
+ /**
94
+ * The version of the file at `path` as a writer read it: what an atomic
95
+ * replace changes (the inode) and what an in-place write changes (size and
96
+ * times). `"absent"` when there is no file.
97
+ */
98
+ export function fileVersion(path) {
99
+ try {
100
+ const s = statSync(path);
101
+ return `${s.dev}:${s.ino}:${s.size}:${s.mtimeMs}:${s.ctimeMs}`;
102
+ }
103
+ catch (err) {
104
+ if (isMissing(err))
105
+ return "absent";
106
+ throw err;
107
+ }
108
+ }
109
+ /** Remove what crashed writers left in `dir`: old reclaimed locks and temp files. */
110
+ function sweep(dir) {
111
+ try {
112
+ for (const name of readdirSync(dir)) {
113
+ if (!name.startsWith(`${LOCKED_FILE}.lock.stale.`) && !(name.startsWith(`${LOCKED_FILE}.`) && name.endsWith(".tmp"))) {
114
+ continue;
115
+ }
116
+ const path = join(dir, name);
117
+ if (Date.now() - statSync(path).mtimeMs > LOCK_STALE_MS)
118
+ unlinkSync(path);
119
+ }
120
+ }
121
+ catch {
122
+ // Best effort.
123
+ }
124
+ }
125
+ /**
126
+ * Take the lock on the credentials file in `dir`, waiting up to
127
+ * {@link LOCK_WAIT_MS} for another writer and reclaiming a stale lock. Throws
128
+ * {@link CredentialsLockedError} naming the lock when it stays held.
129
+ */
130
+ export function acquireCredentialsLock(dir) {
131
+ const path = join(dir, `${LOCKED_FILE}.lock`);
132
+ const token = `${process.pid}:${randomBytes(8).toString("hex")}`;
133
+ const deadline = Date.now() + LOCK_WAIT_MS;
134
+ for (;;) {
135
+ try {
136
+ writeFileSync(path, token, { flag: "wx", mode: 0o600 });
137
+ break;
138
+ }
139
+ catch (err) {
140
+ if (err?.code !== "EEXIST")
141
+ throw err;
142
+ }
143
+ let held;
144
+ let seen;
145
+ try {
146
+ held = statSync(path);
147
+ seen = readFileSync(path, "utf-8");
148
+ }
149
+ catch (err) {
150
+ if (isMissing(err))
151
+ continue;
152
+ throw err;
153
+ }
154
+ if (Date.now() - held.mtimeMs > LOCK_STALE_MS) {
155
+ reclaimStaleLock(path, token, seen);
156
+ continue;
157
+ }
158
+ if (Date.now() >= deadline)
159
+ throw new CredentialsLockedError(path);
160
+ sleep(RETRY_MS);
161
+ }
162
+ const ownsLock = () => {
163
+ try {
164
+ return readFileSync(path, "utf-8") === token;
165
+ }
166
+ catch {
167
+ return false;
168
+ }
169
+ };
170
+ return {
171
+ path,
172
+ token,
173
+ ownsLock,
174
+ release() {
175
+ try {
176
+ if (ownsLock())
177
+ unlinkSync(path);
178
+ }
179
+ catch {
180
+ // Already gone.
181
+ }
182
+ sweep(dir);
183
+ },
184
+ };
185
+ }
186
+ /**
187
+ * Replace `path` with `content` while `lock` is held: write a private
188
+ * temporary file beside it, check the lock is still this holder's and, when
189
+ * `readVersion` is given, that `path` is still the {@link fileVersion} the
190
+ * holder read under the lock, then rename the temporary file over `path`. A
191
+ * holder that lost the lock, or whose read another writer's commit has
192
+ * overtaken, removes its temporary file and throws
193
+ * {@link CredentialsLockedError}, leaving `path` as it was.
194
+ */
195
+ export function replaceFileUnderLock(lock, path, content, readVersion) {
196
+ const tmp = join(dirname(path), `${basename(path)}.${process.pid}.${randomBytes(6).toString("hex")}.tmp`);
197
+ writeFileSync(tmp, content, { mode: 0o600 });
198
+ try {
199
+ if (!lock.ownsLock())
200
+ throw new CredentialsLockedError(lock.path);
201
+ if (readVersion !== undefined && fileVersion(path) !== readVersion) {
202
+ throw new CredentialsLockedError(lock.path);
203
+ }
204
+ renameSync(tmp, path);
205
+ }
206
+ catch (err) {
207
+ try {
208
+ unlinkSync(tmp);
209
+ }
210
+ catch {
211
+ // Never written, or already renamed.
212
+ }
213
+ throw err;
214
+ }
215
+ try {
216
+ chmodSync(path, 0o600);
217
+ }
218
+ catch {
219
+ // Best-effort on Windows / unusual filesystems.
220
+ }
221
+ }
222
+ //# sourceMappingURL=credentials-lock.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"credentials-lock.js","sourceRoot":"","sources":["../../../src/lib/credentials-lock.ts"],"names":[],"mappings":"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"}
@@ -42,6 +42,21 @@
42
42
  * the committed environment whose tree it shares — has no slot of its own: it
43
43
  * reads and writes its parent's, so one login and one logout cover the parent
44
44
  * and all of its children.
45
+ *
46
+ * A slot records the server that issued it (#4492). A slot is keyed by
47
+ * environment name while the environment's `apiUrl` is committed, so a branch
48
+ * switch can point `dev` at another server than the one its stored tokens
49
+ * came from. A slot recording another server than the invoking environment's
50
+ * is never used (its tokens are not sent), and credentials from another server
51
+ * than the slot's environment's are never stored. A slot written before this
52
+ * records no server; it is read against the environment's `apiUrl`, and its
53
+ * next write records one.
54
+ *
55
+ * In a linked git worktree the project's file is the main worktree's
56
+ * (#4492, `credentials-home.ts`), so one login serves every worktree of the
57
+ * repository that holds the project. Every project-mode write is a locked
58
+ * read-modify-write with an atomic replace (`credentials-lock.ts`), because
59
+ * several processes — one per worktree — write that one file.
45
60
  */
46
61
  import type { Credentials } from "../types/index.js";
47
62
  export declare const PROJECT_LOCAL_DIR = ".primitive";
@@ -53,6 +68,12 @@ export declare function isProjectMode(): boolean;
53
68
  * whether we're in project mode or legacy mode.
54
69
  */
55
70
  export declare function getCredentialsFilePath(): string;
71
+ /**
72
+ * The credentials file of the project at `projectRoot`: its own
73
+ * `.primitive/credentials.json`, or in a linked git worktree the main
74
+ * worktree's for the same project (#4492).
75
+ */
76
+ export declare function credentialsFileForProject(projectRoot: string): string;
56
77
  /**
57
78
  * Declare that THIS command may fall back to `~/.primitive/credentials.json`
58
79
  * when no project is in scope. Called at the top of `login`, `logout`, `init`
@@ -65,6 +86,12 @@ export declare function allowGlobalCredentials(): void;
65
86
  * boundary in one process, the way `resetEnvResolver()` does for the resolver.
66
87
  */
67
88
  export declare function resetGlobalCredentialsAllowance(): void;
89
+ /**
90
+ * Whether two server URLs name the same server: scheme and host compare
91
+ * case-insensitively and the path with trailing slashes removed. A URL that
92
+ * does not parse compares as its trailing-slash-trimmed string.
93
+ */
94
+ export declare function sameServer(a: string, b: string): boolean;
68
95
  /** The variable a CI job puts its admin token in (#3890). */
69
96
  export declare const PRIMITIVE_TOKEN_VAR = "PRIMITIVE_TOKEN";
70
97
  /** The token in `PRIMITIVE_TOKEN`, or null when it is unset or blank. */
@@ -84,6 +111,13 @@ export declare function environmentToken(): string | null;
84
111
  * refusal: the environment is resolved first, exactly as for a stored login.
85
112
  */
86
113
  export declare function loadCredentialsStore(): Credentials | null;
114
+ /**
115
+ * The current environment's stored login when its slot records another server
116
+ * than the environment's, blended with the server it RECORDS; null otherwise.
117
+ * For `logout` alone, which revokes such a login at the server that issued it
118
+ * and so never sends its tokens anywhere else.
119
+ */
120
+ export declare function mismatchedLogin(): Credentials | null;
87
121
  /**
88
122
  * The environments that share the current environment's login (#4511): the
89
123
  * one whose slot holds it, and its child environments on this machine. Null
@@ -93,6 +127,21 @@ export declare function loginEnvironments(): {
93
127
  parent: string;
94
128
  children: string[];
95
129
  } | null;
130
+ /**
131
+ * What a login, logout or not-logged-in refusal adds about a linked git
132
+ * worktree (#4492). Each is null when it has nothing to say: outside a
133
+ * project, and where the project's login is its own.
134
+ */
135
+ export declare const worktreeLoginNotes: {
136
+ /** After a login stored in another worktree's file: where it went. */
137
+ storedElsewhere(): string | null;
138
+ /** After a logout of `slot`'s login, when more than one worktree uses it. */
139
+ sharedLogout(slot: string): string | null;
140
+ /** A linked worktree's own credentials file, which is never read. */
141
+ ignoredFile(): string | null;
142
+ /** After "not logged in", when git is why a linked worktree has no login. */
143
+ notLoggedIn(): string | null;
144
+ };
96
145
  /**
97
146
  * The logins still held in the slots of the current login's child
98
147
  * environments: sessions signed in to a child before children used their
@@ -105,9 +154,10 @@ export declare function leftoverChildLogins(): Credentials[];
105
154
  * Saves credentials. In project mode, writes to the per-env slot in the
106
155
  * project credentials file. In legacy mode, writes the global file.
107
156
  *
108
- * The serverUrl on the incoming Credentials is used as-is for legacy
109
- * mode, and ignored in project mode (the env's apiUrl is canonical). In a
110
- * child environment the parent's slot is written (#4511).
157
+ * In project mode the slot records the incoming `serverUrl`, which must be
158
+ * the same server as the slot's environment's `apiUrl`: credentials from
159
+ * another server are refused with `server-mismatch` and nothing is written
160
+ * (#4492). In a child environment the parent's slot is written (#4511).
111
161
  */
112
162
  export declare function saveCredentialsStore(credentials: Credentials): void;
113
163
  /**
@@ -144,7 +194,9 @@ export declare function removeCredentialsSlotInProject(projectRoot: string, envN
144
194
  * Writes the credentials slot for the named environment in a specific
145
195
  * project's credentials file. Used by `primitive init` to carry the session
146
196
  * it just authenticated into the freshly scaffolded project, so the first
147
- * in-project CLI command works without a second `primitive login`.
197
+ * in-project CLI command works without a second `primitive login`. The slot
198
+ * records the credentials' server, which must be the named environment's
199
+ * `apiUrl` in the project's config (#4492).
148
200
  */
149
201
  export declare function seedCredentialsForEnvInProject(projectRoot: string, envName: string, credentials: Credentials): void;
150
202
  /**