@cat-factory/server 0.294.0 → 0.295.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.
Files changed (31) hide show
  1. package/dist/agents/promptOverrides.d.ts +3 -4
  2. package/dist/agents/promptOverrides.d.ts.map +1 -1
  3. package/dist/agents/promptOverrides.js +5 -9
  4. package/dist/agents/promptOverrides.js.map +1 -1
  5. package/dist/github/FetchGitHubClient.d.ts +2 -0
  6. package/dist/github/FetchGitHubClient.d.ts.map +1 -1
  7. package/dist/github/FetchGitHubClient.js +17 -91
  8. package/dist/github/FetchGitHubClient.js.map +1 -1
  9. package/dist/github/githubHttpHelpers.d.ts +59 -3
  10. package/dist/github/githubHttpHelpers.d.ts.map +1 -1
  11. package/dist/github/githubHttpHelpers.js +59 -4
  12. package/dist/github/githubHttpHelpers.js.map +1 -1
  13. package/dist/github/repoContents.d.ts +31 -0
  14. package/dist/github/repoContents.d.ts.map +1 -0
  15. package/dist/github/repoContents.js +121 -0
  16. package/dist/github/repoContents.js.map +1 -0
  17. package/dist/http/env.d.ts +10 -1
  18. package/dist/http/env.d.ts.map +1 -1
  19. package/dist/modules/publicApi/PublicProvisioningController.d.ts +57 -1
  20. package/dist/modules/publicApi/PublicProvisioningController.d.ts.map +1 -1
  21. package/dist/modules/publicApi/PublicProvisioningController.js +204 -8
  22. package/dist/modules/publicApi/PublicProvisioningController.js.map +1 -1
  23. package/dist/modules/publicApi/boardProjection.d.ts +9 -3
  24. package/dist/modules/publicApi/boardProjection.d.ts.map +1 -1
  25. package/dist/modules/publicApi/boardProjection.js +41 -8
  26. package/dist/modules/publicApi/boardProjection.js.map +1 -1
  27. package/dist/modules/publicApi/openapiDocument.generated.d.ts +1 -1
  28. package/dist/modules/publicApi/openapiDocument.generated.d.ts.map +1 -1
  29. package/dist/modules/publicApi/openapiDocument.generated.js +1 -1
  30. package/dist/modules/publicApi/openapiDocument.generated.js.map +1 -1
  31. package/package.json +8 -8
@@ -0,0 +1,31 @@
1
+ import type { GitHubRepoRef, RepoContentEntry, RepoEntry, RepoFileContent } from '@cat-factory/kernel';
2
+ /** The narrow slice of `FetchGitHubClient.request` these reads need. */
3
+ export type ContentsRequest = (path: string, opts: {
4
+ installationId: number;
5
+ }) => Promise<{
6
+ json: unknown;
7
+ }>;
8
+ /** The repo root's entries, or `[]` for a repository with no commits yet. */
9
+ export declare function readRootEntries(request: ContentsRequest, installationId: number, ref: GitHubRepoRef): Promise<RepoEntry[]>;
10
+ /** One directory's entries on `gitRef`, or `[]` for a missing path (mirrors {@link readRootEntries}). */
11
+ export declare function readDirectory(request: ContentsRequest, installationId: number, ref: GitHubRepoRef, path: string, gitRef?: string): Promise<RepoContentEntry[]>;
12
+ /**
13
+ * The WHOLE tree in one recursive git-trees read, so file search never walks the contents API
14
+ * directory by directory. `HEAD` resolves to the repository's default branch.
15
+ */
16
+ export declare function readTree(request: ContentsRequest, installationId: number, ref: GitHubRepoRef, gitRef?: string): Promise<RepoContentEntry[]>;
17
+ /**
18
+ * One file's content and blob sha on `gitRef`, or null when the path is absent.
19
+ *
20
+ * The two refusals this endpoint states badly are translated HERE, because this is the only layer
21
+ * that knows the contents API's own ceiling:
22
+ *
23
+ * - An over-limit blob arrives EITHER as a `403` whose body says `too_large`, or as a `200` with
24
+ * an empty body and `encoding: 'none'`. Left bare, the first reaches a consumer as the same fact
25
+ * as a revoked token (the public file read turned it into a `503` telling an operator to re-mint
26
+ * a credential that worked), and the second as a file that is genuinely empty.
27
+ * - A file whose bytes are not UTF-8 rides back with `lossy: true` rather than pretending the
28
+ * replacement characters are its content.
29
+ */
30
+ export declare function readFileContent(request: ContentsRequest, installationId: number, ref: GitHubRepoRef, path: string, gitRef?: string): Promise<RepoFileContent | null>;
31
+ //# sourceMappingURL=repoContents.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"repoContents.d.ts","sourceRoot":"","sources":["../../src/github/repoContents.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,aAAa,EACb,gBAAgB,EAChB,SAAS,EACT,eAAe,EAChB,MAAM,qBAAqB,CAAA;AAyB5B,wEAAwE;AACxE,MAAM,MAAM,eAAe,GAAG,CAC5B,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE;IAAE,cAAc,EAAE,MAAM,CAAA;CAAE,KAC7B,OAAO,CAAC;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,CAAC,CAAA;AAE/B,6EAA6E;AAC7E,wBAAsB,eAAe,CACnC,OAAO,EAAE,eAAe,EACxB,cAAc,EAAE,MAAM,EACtB,GAAG,EAAE,aAAa,GACjB,OAAO,CAAC,SAAS,EAAE,CAAC,CActB;AAED,yGAAyG;AACzG,wBAAsB,aAAa,CACjC,OAAO,EAAE,eAAe,EACxB,cAAc,EAAE,MAAM,EACtB,GAAG,EAAE,aAAa,EAClB,IAAI,EAAE,MAAM,EACZ,MAAM,CAAC,EAAE,MAAM,GACd,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAuB7B;AAED;;;GAGG;AACH,wBAAsB,QAAQ,CAC5B,OAAO,EAAE,eAAe,EACxB,cAAc,EAAE,MAAM,EACtB,GAAG,EAAE,aAAa,EAClB,MAAM,CAAC,EAAE,MAAM,GACd,OAAO,CAAC,gBAAgB,EAAE,CAAC,CA2B7B;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,eAAe,CACnC,OAAO,EAAE,eAAe,EACxB,cAAc,EAAE,MAAM,EACtB,GAAG,EAAE,aAAa,EAClB,IAAI,EAAE,MAAM,EACZ,MAAM,CAAC,EAAE,MAAM,GACd,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAsBjC"}
@@ -0,0 +1,121 @@
1
+ import { VcsBlobTooLargeError } from '@cat-factory/kernel';
2
+ import { GITHUB_CONTENTS_MAX_BYTES, decodeRepoFileBase64, githubApiStatus, isGitHubBlobTooLarge, } from './githubHttpHelpers.js';
3
+ /** The repo root's entries, or `[]` for a repository with no commits yet. */
4
+ export async function readRootEntries(request, installationId, ref) {
5
+ let json;
6
+ try {
7
+ ;
8
+ ({ json } = await request(`/repos/${ref.owner}/${ref.repo}/contents/`, { installationId }));
9
+ }
10
+ catch (err) {
11
+ // An empty repository has no default branch, so the contents endpoint 404s. That's the signal we
12
+ // want — treat it as "no entries", not an error.
13
+ if (githubApiStatus(err) === 404)
14
+ return [];
15
+ throw err;
16
+ }
17
+ const entries = Array.isArray(json)
18
+ ? json
19
+ : [];
20
+ return entries.map((e) => ({ path: e.path ?? e.name ?? '', type: e.type ?? 'file' }));
21
+ }
22
+ /** One directory's entries on `gitRef`, or `[]` for a missing path (mirrors {@link readRootEntries}). */
23
+ export async function readDirectory(request, installationId, ref, path, gitRef) {
24
+ const clean = path.replace(/^\/+|\/+$/g, '');
25
+ const query = gitRef ? `?ref=${encodeURIComponent(gitRef)}` : '';
26
+ let json;
27
+ try {
28
+ ;
29
+ ({ json } = await request(`/repos/${ref.owner}/${ref.repo}/contents/${clean}${query}`, {
30
+ installationId,
31
+ }));
32
+ }
33
+ catch (err) {
34
+ if (githubApiStatus(err) === 404)
35
+ return [];
36
+ throw err;
37
+ }
38
+ // A directory returns an array; a single file returns an object — coerce both.
39
+ const arr = Array.isArray(json) ? json : [json];
40
+ return arr.map((e) => ({
41
+ path: e.path ?? e.name ?? '',
42
+ name: e.name ?? (e.path ?? '').split('/').pop() ?? '',
43
+ type: e.type ?? 'file',
44
+ sha: e.sha ?? '',
45
+ ...(typeof e.size === 'number' ? { size: e.size } : {}),
46
+ }));
47
+ }
48
+ /**
49
+ * The WHOLE tree in one recursive git-trees read, so file search never walks the contents API
50
+ * directory by directory. `HEAD` resolves to the repository's default branch.
51
+ */
52
+ export async function readTree(request, installationId, ref, gitRef) {
53
+ const treeRef = encodeURIComponent(gitRef && gitRef !== 'HEAD' ? gitRef : 'HEAD');
54
+ let json;
55
+ try {
56
+ ;
57
+ ({ json } = await request(`/repos/${ref.owner}/${ref.repo}/git/trees/${treeRef}?recursive=1`, {
58
+ installationId,
59
+ }));
60
+ }
61
+ catch (err) {
62
+ // Empty repo / unknown ref → no entries (mirrors readDirectory).
63
+ if (githubApiStatus(err) === 404)
64
+ return [];
65
+ throw err;
66
+ }
67
+ const body = json;
68
+ const entries = Array.isArray(body.tree) ? body.tree : [];
69
+ // GitHub git-tree `type` is `blob` | `tree` | `commit` (submodule); normalise to the neutral
70
+ // file/dir vocabulary and drop submodules (they have no browsable content here).
71
+ return entries
72
+ .filter((e) => e.type === 'blob' || e.type === 'tree')
73
+ .map((e) => ({
74
+ path: e.path ?? '',
75
+ name: (e.path ?? '').split('/').pop() ?? '',
76
+ type: e.type === 'tree' ? 'dir' : 'file',
77
+ sha: e.sha ?? '',
78
+ ...(typeof e.size === 'number' ? { size: e.size } : {}),
79
+ }));
80
+ }
81
+ /**
82
+ * One file's content and blob sha on `gitRef`, or null when the path is absent.
83
+ *
84
+ * The two refusals this endpoint states badly are translated HERE, because this is the only layer
85
+ * that knows the contents API's own ceiling:
86
+ *
87
+ * - An over-limit blob arrives EITHER as a `403` whose body says `too_large`, or as a `200` with
88
+ * an empty body and `encoding: 'none'`. Left bare, the first reaches a consumer as the same fact
89
+ * as a revoked token (the public file read turned it into a `503` telling an operator to re-mint
90
+ * a credential that worked), and the second as a file that is genuinely empty.
91
+ * - A file whose bytes are not UTF-8 rides back with `lossy: true` rather than pretending the
92
+ * replacement characters are its content.
93
+ */
94
+ export async function readFileContent(request, installationId, ref, path, gitRef) {
95
+ const clean = path.replace(/^\/+/, '');
96
+ const query = gitRef ? `?ref=${encodeURIComponent(gitRef)}` : '';
97
+ let json;
98
+ try {
99
+ ;
100
+ ({ json } = await request(`/repos/${ref.owner}/${ref.repo}/contents/${clean}${query}`, {
101
+ installationId,
102
+ }));
103
+ }
104
+ catch (err) {
105
+ if (githubApiStatus(err) === 404)
106
+ return null;
107
+ if (isGitHubBlobTooLarge(err))
108
+ throw new VcsBlobTooLargeError('github', GITHUB_CONTENTS_MAX_BYTES);
109
+ throw err;
110
+ }
111
+ const file = json;
112
+ if (file.type !== 'file' || typeof file.content !== 'string')
113
+ return null;
114
+ if (file.encoding === 'none')
115
+ throw new VcsBlobTooLargeError('github', GITHUB_CONTENTS_MAX_BYTES);
116
+ const { content, lossy } = file.encoding === 'base64'
117
+ ? decodeRepoFileBase64(file.content)
118
+ : { content: file.content, lossy: false };
119
+ return { content, sha: file.sha ?? '', ...(lossy ? { lossy: true } : {}) };
120
+ }
121
+ //# sourceMappingURL=repoContents.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"repoContents.js","sourceRoot":"","sources":["../../src/github/repoContents.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAA;AAO1D,OAAO,EACL,yBAAyB,EACzB,oBAAoB,EACpB,eAAe,EACf,oBAAoB,GACrB,MAAM,wBAAwB,CAAA;AAyB/B,6EAA6E;AAC7E,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,OAAwB,EACxB,cAAsB,EACtB,GAAkB;IAElB,IAAI,IAAa,CAAA;IACjB,IAAI,CAAC;QACH,CAAC;QAAA,CAAC,EAAE,IAAI,EAAE,GAAG,MAAM,OAAO,CAAC,UAAU,GAAG,CAAC,KAAK,IAAI,GAAG,CAAC,IAAI,YAAY,EAAE,EAAE,cAAc,EAAE,CAAC,CAAC,CAAA;IAC9F,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,iGAAiG;QACjG,iDAAiD;QACjD,IAAI,eAAe,CAAC,GAAG,CAAC,KAAK,GAAG;YAAE,OAAO,EAAE,CAAA;QAC3C,MAAM,GAAG,CAAA;IACX,CAAC;IACD,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC;QACjC,CAAC,CAAE,IAA+D;QAClE,CAAC,CAAC,EAAE,CAAA;IACN,OAAO,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,IAAI,IAAI,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,IAAI,MAAM,EAAE,CAAC,CAAC,CAAA;AACvF,CAAC;AAED,yGAAyG;AACzG,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,OAAwB,EACxB,cAAsB,EACtB,GAAkB,EAClB,IAAY,EACZ,MAAe;IAEf,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,YAAY,EAAE,EAAE,CAAC,CAAA;IAC5C,MAAM,KAAK,GAAG,MAAM,CAAC,CAAC,CAAC,QAAQ,kBAAkB,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAA;IAChE,IAAI,IAAa,CAAA;IACjB,IAAI,CAAC;QACH,CAAC;QAAA,CAAC,EAAE,IAAI,EAAE,GAAG,MAAM,OAAO,CAAC,UAAU,GAAG,CAAC,KAAK,IAAI,GAAG,CAAC,IAAI,aAAa,KAAK,GAAG,KAAK,EAAE,EAAE;YACtF,cAAc;SACf,CAAC,CAAC,CAAA;IACL,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,eAAe,CAAC,GAAG,CAAC,KAAK,GAAG;YAAE,OAAO,EAAE,CAAA;QAC3C,MAAM,GAAG,CAAA;IACX,CAAC;IACD,+EAA+E;IAC/E,MAAM,GAAG,GAAG,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAA;IAC/C,OACE,GACD,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QACZ,IAAI,EAAE,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,IAAI,IAAI,EAAE;QAC5B,IAAI,EAAE,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,IAAI,EAAE;QACrD,IAAI,EAAE,CAAC,CAAC,IAAI,IAAI,MAAM;QACtB,GAAG,EAAE,CAAC,CAAC,GAAG,IAAI,EAAE;QAChB,GAAG,CAAC,OAAO,CAAC,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACxD,CAAC,CAAC,CAAA;AACL,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,QAAQ,CAC5B,OAAwB,EACxB,cAAsB,EACtB,GAAkB,EAClB,MAAe;IAEf,MAAM,OAAO,GAAG,kBAAkB,CAAC,MAAM,IAAI,MAAM,KAAK,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAA;IACjF,IAAI,IAAa,CAAA;IACjB,IAAI,CAAC;QACH,CAAC;QAAA,CAAC,EAAE,IAAI,EAAE,GAAG,MAAM,OAAO,CAAC,UAAU,GAAG,CAAC,KAAK,IAAI,GAAG,CAAC,IAAI,cAAc,OAAO,cAAc,EAAE;YAC7F,cAAc;SACf,CAAC,CAAC,CAAA;IACL,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,iEAAiE;QACjE,IAAI,eAAe,CAAC,GAAG,CAAC,KAAK,GAAG;YAAE,OAAO,EAAE,CAAA;QAC3C,MAAM,GAAG,CAAA;IACX,CAAC;IACD,MAAM,IAAI,GAAG,IAEZ,CAAA;IACD,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAA;IACzD,6FAA6F;IAC7F,iFAAiF;IACjF,OAAO,OAAO;SACX,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,MAAM,IAAI,CAAC,CAAC,IAAI,KAAK,MAAM,CAAC;SACrD,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QACX,IAAI,EAAE,CAAC,CAAC,IAAI,IAAI,EAAE;QAClB,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,IAAI,EAAE;QAC3C,IAAI,EAAE,CAAC,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM;QACxC,GAAG,EAAE,CAAC,CAAC,GAAG,IAAI,EAAE;QAChB,GAAG,CAAC,OAAO,CAAC,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACxD,CAAC,CAAC,CAAA;AACP,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,OAAwB,EACxB,cAAsB,EACtB,GAAkB,EAClB,IAAY,EACZ,MAAe;IAEf,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAA;IACtC,MAAM,KAAK,GAAG,MAAM,CAAC,CAAC,CAAC,QAAQ,kBAAkB,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAA;IAChE,IAAI,IAAa,CAAA;IACjB,IAAI,CAAC;QACH,CAAC;QAAA,CAAC,EAAE,IAAI,EAAE,GAAG,MAAM,OAAO,CAAC,UAAU,GAAG,CAAC,KAAK,IAAI,GAAG,CAAC,IAAI,aAAa,KAAK,GAAG,KAAK,EAAE,EAAE;YACtF,cAAc;SACf,CAAC,CAAC,CAAA;IACL,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,eAAe,CAAC,GAAG,CAAC,KAAK,GAAG;YAAE,OAAO,IAAI,CAAA;QAC7C,IAAI,oBAAoB,CAAC,GAAG,CAAC;YAC3B,MAAM,IAAI,oBAAoB,CAAC,QAAQ,EAAE,yBAAyB,CAAC,CAAA;QACrE,MAAM,GAAG,CAAA;IACX,CAAC;IACD,MAAM,IAAI,GAAG,IAA4E,CAAA;IACzF,IAAI,IAAI,CAAC,IAAI,KAAK,MAAM,IAAI,OAAO,IAAI,CAAC,OAAO,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAA;IACzE,IAAI,IAAI,CAAC,QAAQ,KAAK,MAAM;QAAE,MAAM,IAAI,oBAAoB,CAAC,QAAQ,EAAE,yBAAyB,CAAC,CAAA;IACjG,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,GACtB,IAAI,CAAC,QAAQ,KAAK,QAAQ;QACxB,CAAC,CAAC,oBAAoB,CAAC,IAAI,CAAC,OAAO,CAAC;QACpC,CAAC,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,CAAA;IAC7C,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,IAAI,EAAE,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAA;AAC5E,CAAC"}
@@ -1,5 +1,5 @@
1
1
  import type { Context } from 'hono';
2
- import type { AgentRunRepository, AuthAttemptRepository, ExecutionRepository, MachineNodeRepository, NotificationChannel, PlatformAlertSink, ResolveBinaryArtifactStore, ConsensusSessionRepository, ResolveRunRepoContext, LocalVcsSetup, Logger, SealedSecretInventory, SecretCipher, ToolSecretResolver, UserRepoAccessRepository, VcsIdentityRegistry, VcsProviderRegistry, VcsWebhookSink, WorkspacePermission, WorkspaceRole } from '@cat-factory/kernel';
2
+ import type { AgentRunRepository, AuthAttemptRepository, ExecutionRepository, MachineNodeRepository, NotificationChannel, PlatformAlertSink, ResolveBinaryArtifactStore, ConsensusSessionRepository, ResolveRepoFilesForCoords, ResolveRunRepoContext, LocalVcsSetup, Logger, SealedSecretInventory, SecretCipher, ToolSecretResolver, UserRepoAccessRepository, VcsIdentityRegistry, VcsProviderRegistry, VcsWebhookSink, WorkspacePermission, WorkspaceRole } from '@cat-factory/kernel';
3
3
  import type { ApiKeyService, EnvironmentBackendRegistry, LocalModelEndpointService, LocalSettingsService, OpenRouterCatalogService, PersonalSubscriptionService, ProviderSubscriptionService, NotificationWebhookService, McpAuthorizationServer, PublicApiKeyService, RunnerBackendRegistry, TestSecretsService, CapabilityCredentialsService, McpOAuthService, ValidationConfigService, UserSecretService } from '@cat-factory/integrations';
4
4
  import type { Core } from '@cat-factory/orchestration';
5
5
  import type { AgentKindRegistry } from '@cat-factory/agents';
@@ -76,6 +76,15 @@ export interface ServerContainer extends Core {
76
76
  * spec endpoint returns an empty view.
77
77
  */
78
78
  resolveRunRepoContext?: ResolveRunRepoContext;
79
+ /**
80
+ * The BLOCK-LESS sibling of {@link resolveRunRepoContext}: the same checkout-free
81
+ * {@link RepoFiles}, from repo coordinates a caller names.
82
+ * `GET /api/v1/repos/:owner/:name/contents?path=` reads through it (the path is a QUERY value, not
83
+ * a path segment: see the contract), so the public file read is scoped to what this workspace has
84
+ * LINKED rather than to whatever the deployment's credential can reach. Present only when a VCS is
85
+ * wired (both facades compose it with `makeResolveRepoFilesForCoords`); absent → the read is a 503.
86
+ */
87
+ resolveRepoFilesForCoords?: ResolveRepoFilesForCoords;
79
88
  /**
80
89
  * Resolve the repo (installation + owner/name + default branch) linked to a
81
90
  * block's enclosing service frame — the same ancestry walk the container executor
@@ -1 +1 @@
1
- {"version":3,"file":"env.d.ts","sourceRoot":"","sources":["../../src/http/env.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"env.d.ts","sourceRoot":"","sources":["../../src/http/env.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,6 @@
1
- import { type BootstrapJob, type GitHubAvailableRepo, type GitHubConnection, type KubernetesManifestSource, type PublicAvailableRepo, type PublicBootstrapJob, type PublicKubernetesManifestSource, type ServiceProvisioning, type UpdatePublicServiceInput } from '@cat-factory/contracts';
1
+ import { type BootstrapJob, type EnvironmentHandlerView, type GitHubAvailableRepo, type GitHubConnection, type KubernetesManifestSource, type PublicAvailableRepo, type PublicBootstrapJob, type PublicEnvironmentHandler, type PublicKubernetesManifestSource, type ServiceProvisioning, type UpdatePublicServiceInput } from '@cat-factory/contracts';
2
2
  import type { RepoUseByRepoId } from '@cat-factory/orchestration';
3
+ import { type RepoFileContent, type RunRepoContext } from '@cat-factory/kernel';
3
4
  import { Hono } from 'hono';
4
5
  import type { AppEnv, ServerContainer } from '../../http/env.js';
5
6
  /**
@@ -64,11 +65,66 @@ export declare function toPublicAvailableRepo(repo: GitHubAvailableRepo, use: Re
64
65
  * prevent, on the deployment least able to tell that is what happened.
65
66
  */
66
67
  export declare function asVcsRefusal(error: unknown): unknown;
68
+ /**
69
+ * Read one file, turning the two answers only this layer can name into the refusals it documents.
70
+ *
71
+ * **A blob past the PROVIDER's own contents ceiling is the same refusal as one past ours.** GitHub
72
+ * reports it as a `403`, which {@link asVcsRefusal} would otherwise read as a rejected credential,
73
+ * so an operator whose lockfile is 1.4 MB would be told to re-mint a token that works. The adapter
74
+ * names the fact (`VcsBlobTooLargeError`) and this maps it onto `file_too_large` with the provider's
75
+ * limit and NO size, because nothing here measured one.
76
+ *
77
+ * **Bytes that are not UTF-8 are refused rather than answered.** The decode is lossy for a PNG, a
78
+ * tarball or a Latin-1 source file, and handing back replacement characters under a field documented
79
+ * as the file's content is the same lie the truncation above refuses to tell: a caller comparing
80
+ * against its own copy sees a mismatch it cannot attribute. The `sha` rides the refusal, so the
81
+ * byte-exact join a grader wanted is still available.
82
+ *
83
+ * Anything else is a fact about the provider and goes through {@link asVcsRefusal} unchanged.
84
+ */
85
+ export declare function readGradableFile(repo: RunRepoContext['repo'], at: string, path: string, ref: string | undefined): Promise<RepoFileContent | null>;
67
86
  /**
68
87
  * Lower a public manifest source onto the internal one, for the same reason and with the same
69
88
  * exhaustiveness as the URL source above.
70
89
  */
71
90
  export declare function toKubernetesManifestSource(source: PublicKubernetesManifestSource): KubernetesManifestSource;
91
+ /**
92
+ * Project a handler for the LIST, whatever engine services it.
93
+ *
94
+ * `engine` and `backendKind` are open strings here rather than the connect view's literal, because
95
+ * this read reports handlers the deployment SEEDED from its composition root, and a deployment may
96
+ * register an environment backend of its own: the whole point of the registry. Reporting one as
97
+ * `kubernetes` would be the coercion the board projection refuses next door, and omitting it would
98
+ * make a seeded handler indistinguishable from an absent one, which is the state this read exists to
99
+ * make checkable.
100
+ *
101
+ * `endpoint` and not `apiServerUrl`: the Kubernetes noun is false of every other engine, and stating
102
+ * it anyway is what sends an operator whose environment is a VM looking for an apiserver.
103
+ *
104
+ * BOTH manifest-id fields are reported, because the engine's own resolution
105
+ * (`resolveInfraHandler` → `matchesCustom`) matches a service's pinned `manifestId` against EITHER,
106
+ * and the two ways of registering a handler each set only one: a deployment SEEDING one keys it with
107
+ * `manifestId`, while a `remote-custom` connection declares `acceptsManifestId`. Publishing only the
108
+ * second made the commonest seed shape read as a handler serving nothing, so a setup script checking
109
+ * that its own seed landed found no entry naming it while a run against it resolved fine.
110
+ *
111
+ * No `config`, though `EnvironmentHandlerView` carries one for the app's connect-form prefill: it is
112
+ * the internal per-engine bag, deliberately open, and this surface may not freeze it (ADR 0034).
113
+ */
114
+ export declare function toPublicHandler(view: EnvironmentHandlerView): PublicEnvironmentHandler;
115
+ /**
116
+ * Order handlers by what IDENTIFIES one, comparing code units rather than collating.
117
+ *
118
+ * `localeCompare` is ICU- and locale-dependent, so the same set of handlers can serialise in one
119
+ * order from a workerd isolate and in another from a Node build with a different ICU: precisely the
120
+ * spurious diff the ordering was added to prevent. A code-unit comparison answers the same everywhere,
121
+ * which is what a wire ordering needs.
122
+ *
123
+ * Both manifest ids join the key, since two `custom` handlers keyed to different manifests are
124
+ * otherwise equal here. A handler that ties on all four keys keeps its position from the repository
125
+ * read (the sort is stable), which is the honest fallback: nothing published distinguishes them.
126
+ */
127
+ export declare function byHandlerIdentity(left: PublicEnvironmentHandler, right: PublicEnvironmentHandler): number;
72
128
  /**
73
129
  * Lower a public service patch onto the board patch.
74
130
  *
@@ -1 +1 @@
1
- {"version":3,"file":"PublicProvisioningController.d.ts","sourceRoot":"","sources":["../../../src/modules/publicApi/PublicProvisioningController.ts"],"names":[],"mappings":"AAAA,OAAO,EAaL,KAAK,YAAY,EAEjB,KAAK,mBAAmB,EACxB,KAAK,gBAAgB,EAErB,KAAK,wBAAwB,EAI7B,KAAK,mBAAmB,EACxB,KAAK,kBAAkB,EAGvB,KAAK,8BAA8B,EAQnC,KAAK,mBAAmB,EACxB,KAAK,wBAAwB,EAC9B,MAAM,wBAAwB,CAAA;AAC/B,OAAO,KAAK,EAKV,eAAe,EAEhB,MAAM,4BAA4B,CAAA;AAWnC,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAE3B,OAAO,KAAK,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;AAgEhE;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,iBAAiB,CACrC,SAAS,EAAE,eAAe,EAC1B,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAQlC;AA6FD,wBAAgB,4BAA4B,IAAI,IAAI,CAAC,MAAM,CAAC,CAQ3D;AAID;;;;;;;;;;GAUG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,YAAY,GAAG,kBAAkB,CAgB1E;AAiDD;;;;;;;;;;;GAWG;AACH,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,mBAAmB,EACzB,GAAG,EAAE,eAAe,GACnB,mBAAmB,CAsBrB;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAoBpD;AAyID;;;GAGG;AACH,wBAAgB,0BAA0B,CACxC,MAAM,EAAE,8BAA8B,GACrC,wBAAwB,CAW1B;AAmFD;;;;;;GAMG;AACH,wBAAgB,YAAY,CAC1B,KAAK,EAAE,wBAAwB,EAC/B,MAAM,EAAE,mBAAmB,GAAG,SAAS,GACtC;IACD,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,YAAY,CAAC,EAAE,mBAAmB,CAAA;CACnC,CAQA"}
1
+ {"version":3,"file":"PublicProvisioningController.d.ts","sourceRoot":"","sources":["../../../src/modules/publicApi/PublicProvisioningController.ts"],"names":[],"mappings":"AAAA,OAAO,EAgBL,KAAK,YAAY,EACjB,KAAK,sBAAsB,EAC3B,KAAK,mBAAmB,EACxB,KAAK,gBAAgB,EAErB,KAAK,wBAAwB,EAI7B,KAAK,mBAAmB,EACxB,KAAK,kBAAkB,EAGvB,KAAK,wBAAwB,EAC7B,KAAK,8BAA8B,EAQnC,KAAK,mBAAmB,EACxB,KAAK,wBAAwB,EAC9B,MAAM,wBAAwB,CAAA;AAC/B,OAAO,KAAK,EAKV,eAAe,EAEhB,MAAM,4BAA4B,CAAA;AAEnC,OAAO,EASL,KAAK,eAAe,EACpB,KAAK,cAAc,EACpB,MAAM,qBAAqB,CAAA;AAE5B,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAE3B,OAAO,KAAK,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;AAgEhE;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,iBAAiB,CACrC,SAAS,EAAE,eAAe,EAC1B,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAQlC;AA6FD,wBAAgB,4BAA4B,IAAI,IAAI,CAAC,MAAM,CAAC,CAQ3D;AAID;;;;;;;;;;GAUG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,YAAY,GAAG,kBAAkB,CAgB1E;AAiDD;;;;;;;;;;;GAWG;AACH,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,mBAAmB,EACzB,GAAG,EAAE,eAAe,GACnB,mBAAmB,CAsBrB;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAoBpD;AAoJD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,gBAAgB,CACpC,IAAI,EAAE,cAAc,CAAC,MAAM,CAAC,EAC5B,EAAE,EAAE,MAAM,EACV,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,MAAM,GAAG,SAAS,GACtB,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAsBjC;AA6DD;;;GAGG;AACH,wBAAgB,0BAA0B,CACxC,MAAM,EAAE,8BAA8B,GACrC,wBAAwB,CAW1B;AA8BD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,sBAAsB,GAAG,wBAAwB,CAYtF;AA8CD;;;;;;;;;;;GAWG;AACH,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,wBAAwB,EAC9B,KAAK,EAAE,wBAAwB,GAC9B,MAAM,CAOR;AAiCD;;;;;;GAMG;AACH,wBAAgB,YAAY,CAC1B,KAAK,EAAE,wBAAwB,EAC/B,MAAM,EAAE,mBAAmB,GAAG,SAAS,GACtC;IACD,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,YAAY,CAAC,EAAE,mBAAmB,CAAA;CACnC,CAQA"}
@@ -1,5 +1,5 @@
1
- import { connectPublicEnvironmentContract, getPublicRepoBootstrapContract, getPublicVcsConnectionContract, linkPublicRepoContract, listPublicAvailableReposContract, listPublicModelPresetsContract, listPublicRiskPoliciesContract, listPublicWiredModelsContract, startPublicRepoBootstrapContract, testPublicEnvironmentConnectionContract, updatePublicServiceContract, UNATTRIBUTED_BLOCK_EDIT_AUTHORITY, } from '@cat-factory/contracts';
2
- import { NotFoundError, RateLimitedError, UnavailableError, VcsApiError, individualVendorForModelId, isVcsRateLimited, } from '@cat-factory/kernel';
1
+ import { connectPublicEnvironmentContract, getPublicRepoBootstrapContract, getPublicRepoFileContract, getPublicVcsConnectionContract, linkPublicRepoContract, listPublicAvailableReposContract, listPublicEnvironmentConnectionsContract, listPublicModelPresetsContract, listPublicRiskPoliciesContract, listPublicWiredModelsContract, MAX_REPO_FILE_CHARS, startPublicRepoBootstrapContract, testPublicEnvironmentConnectionContract, updatePublicServiceContract, UNATTRIBUTED_BLOCK_EDIT_AUTHORITY, } from '@cat-factory/contracts';
2
+ import { NotFoundError, RateLimitedError, UnavailableError, ValidationError, VcsApiError, VcsBlobTooLargeError, individualVendorForModelId, isVcsRateLimited, } from '@cat-factory/kernel';
3
3
  import { buildHonoRoute } from '@toad-contracts/hono';
4
4
  import { Hono } from 'hono';
5
5
  import { requireCapability } from '../../http/guards.js';
@@ -365,6 +365,99 @@ function registerRepoAdoptionRoutes(app) {
365
365
  }
366
366
  return c.json(toPublicRepo(adopted), 200);
367
367
  });
368
+ registerRepoFileRoute(app);
369
+ }
370
+ /**
371
+ * ONE file out of a linked repository, so a caller can grade what a run COMMITTED.
372
+ *
373
+ * It closes the loop on everything else this surface can start. A caller could create a repository,
374
+ * adopt one, file work against it and watch a run merge, and then had nothing to read but the
375
+ * agent's own final reply, which is asserting on model prose: swap the model and it goes red having
376
+ * found nothing wrong. The only real alternative was a SECOND VCS credential in the caller's config,
377
+ * with its own scopes to get right, for data the workspace's own connection already reads.
378
+ *
379
+ * Five outcomes, kept apart because each takes a different action, and because folding any of them
380
+ * onto "no such file" is the same mistake `PublicSpecController` documents at length: a repository
381
+ * that answers nothing and a file that is genuinely absent look identical from a provider. Two of the
382
+ * five are what {@link readGradableFile} exists to name.
383
+ */
384
+ function registerRepoFileRoute(app) {
385
+ buildHonoRoute(app, getPublicRepoFileContract, async (c) => {
386
+ const auth = await authorizeOrThrow(c, getPublicRepoFileContract.minScope);
387
+ const resolve = requireCapability(c.get('container').resolveRepoFilesForCoords, 'No version-control integration is configured for this deployment', 'vcs_not_configured');
388
+ const { owner, name } = c.req.valid('param');
389
+ const { path, ref } = c.req.valid('query');
390
+ // Resolved against the workspace's PROJECTED repos, so this reads only what this workspace has
391
+ // LINKED. A repository the deployment's credential could reach and nobody adopted is absent
392
+ // here exactly as it is from `GET /api/v1/repos`, which is what stops the endpoint becoming a
393
+ // way to read any repository an installation happens to cover.
394
+ const context = await resolve(auth.workspaceId, { owner, repo: name });
395
+ if (!context) {
396
+ throw new NotFoundError('repository', `${owner}/${name}`, { reason: 'repo_not_linked' });
397
+ }
398
+ // `ref` is passed through EXACTLY as the request gave it, absent included, because the port
399
+ // resolves the repository's own default branch for an omitted one. `context.baseBranch` is the
400
+ // wrong value to substitute: `makeResolveRepoFilesForCoords` INVENTS `main` for a projection row
401
+ // carrying no default branch, so a repository whose default is `master` would be read at a
402
+ // branch it does not have and answer `file_not_found` for a file that is right there, while the
403
+ // response named the invented branch as the thing graded.
404
+ const at = `${owner}/${name}:${path}`;
405
+ const file = await readGradableFile(context.repo, at, path, ref);
406
+ if (!file) {
407
+ throw new NotFoundError('file', `${at}@${ref ?? 'the default branch'}`, {
408
+ reason: 'file_not_found',
409
+ });
410
+ }
411
+ if (file.content.length > MAX_REPO_FILE_CHARS) {
412
+ // REFUSED, never truncated. A caller reading a file to grade what an agent committed is
413
+ // joining on its exact bytes, and a silently shortened answer is indistinguishable from an
414
+ // agent that wrote a shorter file. The size and the limit ride `details` because those are
415
+ // facts a caller can act on where a truncation is not.
416
+ //
417
+ // A 422 and not a 413: `Content Too Large` is a statement about the REQUEST entity, and this
418
+ // request is perfectly well formed. The status class is "structurally valid, refused by a
419
+ // domain rule", which is the same reading `resolveRepoTarget` takes for an unlinked service,
420
+ // and the machine-readable cause is `details.reason` as it is everywhere else on this surface.
421
+ throw new ValidationError(`${at} is ${file.content.length} characters, past the ${MAX_REPO_FILE_CHARS} this read ` +
422
+ `answers with`, { reason: 'file_too_large', size: file.content.length, limit: MAX_REPO_FILE_CHARS });
423
+ }
424
+ return c.json({ owner, name, path, ref: ref ?? null, sha: file.sha, content: file.content }, 200);
425
+ });
426
+ }
427
+ /**
428
+ * Read one file, turning the two answers only this layer can name into the refusals it documents.
429
+ *
430
+ * **A blob past the PROVIDER's own contents ceiling is the same refusal as one past ours.** GitHub
431
+ * reports it as a `403`, which {@link asVcsRefusal} would otherwise read as a rejected credential,
432
+ * so an operator whose lockfile is 1.4 MB would be told to re-mint a token that works. The adapter
433
+ * names the fact (`VcsBlobTooLargeError`) and this maps it onto `file_too_large` with the provider's
434
+ * limit and NO size, because nothing here measured one.
435
+ *
436
+ * **Bytes that are not UTF-8 are refused rather than answered.** The decode is lossy for a PNG, a
437
+ * tarball or a Latin-1 source file, and handing back replacement characters under a field documented
438
+ * as the file's content is the same lie the truncation above refuses to tell: a caller comparing
439
+ * against its own copy sees a mismatch it cannot attribute. The `sha` rides the refusal, so the
440
+ * byte-exact join a grader wanted is still available.
441
+ *
442
+ * Anything else is a fact about the provider and goes through {@link asVcsRefusal} unchanged.
443
+ */
444
+ export async function readGradableFile(repo, at, path, ref) {
445
+ let file;
446
+ try {
447
+ file = await repo.getFile(path, ref);
448
+ }
449
+ catch (error) {
450
+ if (error instanceof VcsBlobTooLargeError) {
451
+ throw new ValidationError(`${at} is past the ${error.limitBytes} bytes ${error.provider} will serve through its ` +
452
+ `contents API, so this read cannot answer with it`, { reason: 'file_too_large', limit: error.limitBytes });
453
+ }
454
+ throw asVcsRefusal(error);
455
+ }
456
+ if (file?.lossy) {
457
+ throw new ValidationError(`${at} is not UTF-8 text, so this read cannot answer with its content; its blob sha is ` +
458
+ `${file.sha}`, { reason: 'file_not_text', sha: file.sha });
459
+ }
460
+ return file;
368
461
  }
369
462
  // ---- the environment connection (the ENGINE half) ---------------------------
370
463
  /**
@@ -449,6 +542,10 @@ function toInfraHandlerConfig(connection) {
449
542
  *
450
543
  * `baseUrl` is the connection's own endpoint, which for a Kubernetes engine is the apiserver, so it
451
544
  * is reported under the name the caller supplied it as rather than the engine's generic one.
545
+ *
546
+ * The CONNECT response only, which is why `engine` can be the literal: this surface registers
547
+ * kubernetes and nothing else, so it is true of every handler this call can produce. The LIST is a
548
+ * different question and takes {@link toPublicHandler}.
452
549
  */
453
550
  function toPublicConnectionView(view) {
454
551
  return {
@@ -459,6 +556,42 @@ function toPublicConnectionView(view) {
459
556
  secretKeys: view.secretKeys,
460
557
  };
461
558
  }
559
+ /**
560
+ * Project a handler for the LIST, whatever engine services it.
561
+ *
562
+ * `engine` and `backendKind` are open strings here rather than the connect view's literal, because
563
+ * this read reports handlers the deployment SEEDED from its composition root, and a deployment may
564
+ * register an environment backend of its own: the whole point of the registry. Reporting one as
565
+ * `kubernetes` would be the coercion the board projection refuses next door, and omitting it would
566
+ * make a seeded handler indistinguishable from an absent one, which is the state this read exists to
567
+ * make checkable.
568
+ *
569
+ * `endpoint` and not `apiServerUrl`: the Kubernetes noun is false of every other engine, and stating
570
+ * it anyway is what sends an operator whose environment is a VM looking for an apiserver.
571
+ *
572
+ * BOTH manifest-id fields are reported, because the engine's own resolution
573
+ * (`resolveInfraHandler` → `matchesCustom`) matches a service's pinned `manifestId` against EITHER,
574
+ * and the two ways of registering a handler each set only one: a deployment SEEDING one keys it with
575
+ * `manifestId`, while a `remote-custom` connection declares `acceptsManifestId`. Publishing only the
576
+ * second made the commonest seed shape read as a handler serving nothing, so a setup script checking
577
+ * that its own seed landed found no entry naming it while a run against it resolved fine.
578
+ *
579
+ * No `config`, though `EnvironmentHandlerView` carries one for the app's connect-form prefill: it is
580
+ * the internal per-engine bag, deliberately open, and this surface may not freeze it (ADR 0034).
581
+ */
582
+ export function toPublicHandler(view) {
583
+ return {
584
+ provisionType: view.provisionType,
585
+ manifestId: view.manifestId,
586
+ acceptsManifestId: view.acceptsManifestId,
587
+ engine: view.engine,
588
+ backendKind: view.backendKind,
589
+ label: view.label,
590
+ endpoint: view.baseUrl,
591
+ secretKeys: view.secretKeys,
592
+ connectedAt: view.connectedAt,
593
+ };
594
+ }
462
595
  function registerEnvironmentRoutes(app) {
463
596
  // Probe without persisting. `ok: false` is an ANSWER (the cluster refused the token), so it is a
464
597
  // 200 carrying the verdict rather than an error: a caller distinguishes "the cluster said no"
@@ -490,6 +623,41 @@ function registerEnvironmentRoutes(app) {
490
623
  });
491
624
  return c.json(toPublicConnectionView(view), 201);
492
625
  });
626
+ // The read half. Ordered so a caller diffing two workspaces (or its own setup before and after)
627
+ // compares two stable lists rather than two insertion orders.
628
+ buildHonoRoute(app, listPublicEnvironmentConnectionsContract, async (c) => {
629
+ const auth = await authorizeOrThrow(c, listPublicEnvironmentConnectionsContract.minScope);
630
+ const environments = requireEnvironments(c);
631
+ const handlers = await environments.connectionService.listHandlers(auth.workspaceId);
632
+ const connections = handlers.map(toPublicHandler).sort(byHandlerIdentity);
633
+ return c.json({ connections }, 200);
634
+ });
635
+ }
636
+ /**
637
+ * Order handlers by what IDENTIFIES one, comparing code units rather than collating.
638
+ *
639
+ * `localeCompare` is ICU- and locale-dependent, so the same set of handlers can serialise in one
640
+ * order from a workerd isolate and in another from a Node build with a different ICU: precisely the
641
+ * spurious diff the ordering was added to prevent. A code-unit comparison answers the same everywhere,
642
+ * which is what a wire ordering needs.
643
+ *
644
+ * Both manifest ids join the key, since two `custom` handlers keyed to different manifests are
645
+ * otherwise equal here. A handler that ties on all four keys keeps its position from the repository
646
+ * read (the sort is stable), which is the honest fallback: nothing published distinguishes them.
647
+ */
648
+ export function byHandlerIdentity(left, right) {
649
+ return (compareCodeUnits(left.provisionType, right.provisionType) ||
650
+ compareCodeUnits(left.manifestId, right.manifestId) ||
651
+ compareCodeUnits(left.acceptsManifestId, right.acceptsManifestId) ||
652
+ compareCodeUnits(left.label, right.label));
653
+ }
654
+ /** An absent id sorts first, which is the only ordering a null can honestly take. */
655
+ function compareCodeUnits(left, right) {
656
+ const a = left ?? '';
657
+ const b = right ?? '';
658
+ if (a === b)
659
+ return 0;
660
+ return a < b ? -1 : 1;
493
661
  }
494
662
  // ---- a service's provisioning (the SOURCE half) -----------------------------
495
663
  function registerServiceRoutes(app) {
@@ -529,7 +697,7 @@ export function toBlockPatch(input, stored) {
529
697
  * Overlay a public provisioning patch onto what the service already declares.
530
698
  *
531
699
  * `provisioning` is ONE JSON column and `updateBlock` REPLACES it wholesale, where this surface
532
- * publishes two of its dozen fields. Writing just the pair would therefore delete every field the
700
+ * publishes a few of its dozen fields. Writing just those would therefore delete every field the
533
701
  * public shape cannot express: a Kubernetes service's `images`, `secretInjections` and
534
702
  * `helmReleases`, authored in the app by someone who is not the caller. The next deploy would
535
703
  * render manifests with no image overrides and no Secrets, which is the "empty environment that
@@ -539,14 +707,42 @@ export function toBlockPatch(input, stored) {
539
707
  * A patch that CHANGES the provision type replaces instead of overlaying: the stored remainder
540
708
  * belongs to the type being left behind, so carrying it forward would attach one engine's
541
709
  * configuration to another.
710
+ *
711
+ * Exhaustive over the public variant through a `never`, so a member added there cannot reach the
712
+ * stored column as an unlowered shape.
542
713
  */
543
714
  function mergeProvisioning(patch, stored) {
544
715
  const base = stored?.type === patch.type ? stored : undefined;
545
- return {
546
- ...base,
547
- type: patch.type,
548
- manifestSource: toKubernetesManifestSource(patch.manifestSource),
549
- };
716
+ switch (patch.type) {
717
+ case 'kubernetes':
718
+ return {
719
+ ...base,
720
+ type: 'kubernetes',
721
+ manifestSource: toKubernetesManifestSource(patch.manifestSource),
722
+ };
723
+ case 'custom': {
724
+ // `manifestPath` is the one stored field this surface PUBLISHES, so it is also the only one a
725
+ // patch can be said to have left out on purpose: omitted, it is CLEARED, and the manifest
726
+ // type's own default applies again. Carried over from the stored row (which is what a plain
727
+ // `...base` does), a caller correcting a path back to the default would keep deploying the old
728
+ // one and have no way to say so, since the public shape has no other way to express "none".
729
+ // Every field the public shape cannot express still rides `...rest`, for the reason the
730
+ // function's own doc gives.
731
+ const { manifestPath: _cleared, ...rest } = base ?? {};
732
+ return {
733
+ ...rest,
734
+ type: 'custom',
735
+ manifestId: patch.manifestId,
736
+ ...(patch.manifestPath === undefined ? {} : { manifestPath: patch.manifestPath }),
737
+ };
738
+ }
739
+ default:
740
+ return unreachableProvisioning(patch);
741
+ }
742
+ }
743
+ /** The compile-time half of the lowering above: a new public member has to be lowered explicitly. */
744
+ function unreachableProvisioning(value) {
745
+ throw new Error(`unmapped public service provisioning: ${JSON.stringify(value)}`);
550
746
  }
551
747
  // ---- what this deployment has WIRED -----------------------------------------
552
748
  /**