@cat-factory/agents 0.95.1 → 0.96.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 (33) hide show
  1. package/dist/agents/kinds/traits.d.ts +22 -0
  2. package/dist/agents/kinds/traits.d.ts.map +1 -1
  3. package/dist/agents/kinds/traits.js +42 -3
  4. package/dist/agents/kinds/traits.js.map +1 -1
  5. package/dist/foundationalServices/FoundationalServiceCatalogService.d.ts +65 -0
  6. package/dist/foundationalServices/FoundationalServiceCatalogService.d.ts.map +1 -0
  7. package/dist/foundationalServices/FoundationalServiceCatalogService.js +284 -0
  8. package/dist/foundationalServices/FoundationalServiceCatalogService.js.map +1 -0
  9. package/dist/foundationalServices/FoundationalServiceRunResolver.d.ts +37 -0
  10. package/dist/foundationalServices/FoundationalServiceRunResolver.d.ts.map +1 -0
  11. package/dist/foundationalServices/FoundationalServiceRunResolver.js +92 -0
  12. package/dist/foundationalServices/FoundationalServiceRunResolver.js.map +1 -0
  13. package/dist/foundationalServices/FoundationalServiceSourceService.d.ts +86 -0
  14. package/dist/foundationalServices/FoundationalServiceSourceService.d.ts.map +1 -0
  15. package/dist/foundationalServices/FoundationalServiceSourceService.js +346 -0
  16. package/dist/foundationalServices/FoundationalServiceSourceService.js.map +1 -0
  17. package/dist/foundationalServices/contract-validation.d.ts +8 -0
  18. package/dist/foundationalServices/contract-validation.d.ts.map +1 -0
  19. package/dist/foundationalServices/contract-validation.js +43 -0
  20. package/dist/foundationalServices/contract-validation.js.map +1 -0
  21. package/dist/foundationalServices/foundational-catalog.d.ts +22 -0
  22. package/dist/foundationalServices/foundational-catalog.d.ts.map +1 -0
  23. package/dist/foundationalServices/foundational-catalog.js +58 -0
  24. package/dist/foundationalServices/foundational-catalog.js.map +1 -0
  25. package/dist/foundationalServices/foundational-source.logic.d.ts +51 -0
  26. package/dist/foundationalServices/foundational-source.logic.d.ts.map +1 -0
  27. package/dist/foundationalServices/foundational-source.logic.js +109 -0
  28. package/dist/foundationalServices/foundational-source.logic.js.map +1 -0
  29. package/dist/index.d.ts +7 -1
  30. package/dist/index.d.ts.map +1 -1
  31. package/dist/index.js +8 -1
  32. package/dist/index.js.map +1 -1
  33. package/package.json +5 -5
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FoundationalServiceRunResolver.js","sourceRoot":"","sources":["../../src/foundationalServices/FoundationalServiceRunResolver.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,86 @@
1
+ import type { FoundationalServiceOwnerKind, FoundationalServiceSource, FoundationalServiceSourceStatus, FoundationalServiceSyncResult, LinkFoundationalServiceSourceInput } from '@cat-factory/contracts';
2
+ import type { ApiContractRepository, Clock, FoundationalServiceRepository, FoundationalServiceSourceRepository, GitHubClient, IdGenerator, Logger } from '@cat-factory/kernel';
3
+ /**
4
+ * Resolve the GitHub App installation id that can read a tier's repos — a workspace source
5
+ * reads through the workspace's installation, an account source through the account's.
6
+ * Returns null when none is available, so a sync fails with a clear error rather than a
7
+ * silent empty pull.
8
+ */
9
+ export type ResolveFoundationalInstallationId = (ownerKind: FoundationalServiceOwnerKind, ownerId: string) => Promise<number | null>;
10
+ export interface FoundationalServiceSourceServiceDependencies {
11
+ foundationalServiceSourceRepository: FoundationalServiceSourceRepository;
12
+ foundationalServiceRepository: FoundationalServiceRepository;
13
+ apiContractRepository: ApiContractRepository;
14
+ githubClient: GitHubClient;
15
+ resolveInstallationId: ResolveFoundationalInstallationId;
16
+ idGenerator: IdGenerator;
17
+ clock: Clock;
18
+ logger?: Logger;
19
+ /** Drops the cached merged catalog for a tier after a sync/unlink changed a row. */
20
+ invalidateCatalog?: (ownerKind: FoundationalServiceOwnerKind, ownerId: string) => Promise<void>;
21
+ }
22
+ /**
23
+ * Repo-sourced foundational services: link a repo FOLDER of service definitions (or an
24
+ * explicit FILE list for one service), resync it, and answer the cheap "check for changes"
25
+ * without writing. Reads go through the tier's existing GitHub installation — no new
26
+ * credential store.
27
+ *
28
+ * The shared repo-source engine (`repoSourceSync`) owns the mechanics that every repo-sourced
29
+ * library gets identically — pin the head commit BEFORE reading, run the reconcile, sweep
30
+ * tombstones by produced id, stamp the sync state, invalidate only on a real change. The
31
+ * differentiator supplied here is what a UNIT is, and there are two of them:
32
+ *
33
+ * - **`directory`**: one service per immediate subdirectory, identified by its `service.md`.
34
+ * - **`files`**: one service, named on the LINK, whose contracts are the linked file paths.
35
+ * There is no directory convention to read identity from, which is exactly why the link
36
+ * carries it (refused at the write boundary otherwise).
37
+ *
38
+ * Both modes anchor their staleness probe on ONE directory (`dirPath`), so a `files` source
39
+ * with a dozen linked files still costs a single commit read per freshness check.
40
+ */
41
+ export declare class FoundationalServiceSourceService {
42
+ private readonly deps;
43
+ private readonly log;
44
+ constructor(deps: FoundationalServiceSourceServiceDependencies);
45
+ /** Linked sources for a tier + their last-synced state. */
46
+ list(ownerKind: FoundationalServiceOwnerKind, ownerId: string): Promise<FoundationalServiceSource[]>;
47
+ /** Link a repo folder (or file list) as a source. Does not sync (call {@link sync}). */
48
+ link(ownerKind: FoundationalServiceOwnerKind, ownerId: string, input: LinkFoundationalServiceSourceInput): Promise<FoundationalServiceSource>;
49
+ /** Unlink a source and tombstone every service it produced. */
50
+ unlink(ownerKind: FoundationalServiceOwnerKind, ownerId: string, sourceId: string): Promise<void>;
51
+ /** The cheap "check for changes": one head-commit read compared to the pinned commit. */
52
+ status(ownerKind: FoundationalServiceOwnerKind, ownerId: string, sourceId: string): Promise<FoundationalServiceSourceStatus>;
53
+ /** Resync a source by id, resolving its owner from the stored row (the sweeper's entry point). */
54
+ syncById(sourceId: string): Promise<FoundationalServiceSyncResult>;
55
+ /** Resync a source: read the tree, upsert changed services, tombstone removed ones. */
56
+ sync(ownerKind: FoundationalServiceOwnerKind, ownerId: string, sourceId: string): Promise<FoundationalServiceSyncResult>;
57
+ /**
58
+ * AUTOREFRESH: resync every source whose last sync is older than `staleAfterMs`, bounded to
59
+ * `limit` per pass. Driven by the periodic sweep on both facades (a Cloudflare cron tick, a
60
+ * Node interval), so a linked repo stays fresh whether or not anyone opens the management
61
+ * surface or starts a run.
62
+ *
63
+ * Best-effort PER SOURCE: one repo whose installation was revoked must not stop the pass from
64
+ * refreshing the rest, so each failure is logged with its cause and the sweep continues. The
65
+ * count returned is sources SYNCED, not sources attempted.
66
+ */
67
+ refreshStale(staleAfterMs: number, limit: number): Promise<number>;
68
+ private runSync;
69
+ /** `directory` mode: one service per immediate subdirectory carrying a `service.md`. */
70
+ private reconcileDirectories;
71
+ /** `files` mode: ONE service, named on the link, whose contracts are the linked paths. */
72
+ private reconcileFiles;
73
+ /**
74
+ * Read the given repo paths and keep the ones that ARE contract documents. A file whose
75
+ * format is unrecognised is skipped rather than stored as an opaque blob: the format is what
76
+ * tells a downstream agent how to read it, and a "contract" nobody can interpret is worse in
77
+ * an agent's context than an absent one.
78
+ */
79
+ private readContracts;
80
+ private writeService;
81
+ private listDir;
82
+ private readFile;
83
+ private require;
84
+ private requireInstallation;
85
+ }
86
+ //# sourceMappingURL=FoundationalServiceSourceService.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FoundationalServiceSourceService.d.ts","sourceRoot":"","sources":["../../src/foundationalServices/FoundationalServiceSourceService.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,4BAA4B,EAC5B,yBAAyB,EACzB,+BAA+B,EAC/B,6BAA6B,EAC7B,kCAAkC,EACnC,MAAM,wBAAwB,CAAA;AAC/B,OAAO,KAAK,EAEV,qBAAqB,EACrB,KAAK,EAEL,6BAA6B,EAE7B,mCAAmC,EACnC,YAAY,EACZ,WAAW,EACX,MAAM,EAEP,MAAM,qBAAqB,CAAA;AAyB5B;;;;;GAKG;AACH,MAAM,MAAM,iCAAiC,GAAG,CAC9C,SAAS,EAAE,4BAA4B,EACvC,OAAO,EAAE,MAAM,KACZ,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAA;AAE3B,MAAM,WAAW,4CAA4C;IAC3D,mCAAmC,EAAE,mCAAmC,CAAA;IACxE,6BAA6B,EAAE,6BAA6B,CAAA;IAC5D,qBAAqB,EAAE,qBAAqB,CAAA;IAC5C,YAAY,EAAE,YAAY,CAAA;IAC1B,qBAAqB,EAAE,iCAAiC,CAAA;IACxD,WAAW,EAAE,WAAW,CAAA;IACxB,KAAK,EAAE,KAAK,CAAA;IACZ,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,oFAAoF;IACpF,iBAAiB,CAAC,EAAE,CAAC,SAAS,EAAE,4BAA4B,EAAE,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;CAChG;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,qBAAa,gCAAgC;IAG/B,OAAO,CAAC,QAAQ,CAAC,IAAI;IAFjC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAQ;IAE5B,YAA6B,IAAI,EAAE,4CAA4C,EAE9E;IAED,2DAA2D;IACrD,IAAI,CACR,SAAS,EAAE,4BAA4B,EACvC,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,yBAAyB,EAAE,CAAC,CAGtC;IAED,wFAAwF;IAClF,IAAI,CACR,SAAS,EAAE,4BAA4B,EACvC,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,kCAAkC,GACxC,OAAO,CAAC,yBAAyB,CAAC,CA4BpC;IAED,+DAA+D;IACzD,MAAM,CACV,SAAS,EAAE,4BAA4B,EACvC,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC,CAgBf;IAED,yFAAyF;IACnF,MAAM,CACV,SAAS,EAAE,4BAA4B,EACvC,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,+BAA+B,CAAC,CAI1C;IAED,kGAAkG;IAC5F,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,6BAA6B,CAAC,CAQvE;IAED,uFAAuF;IACjF,IAAI,CACR,SAAS,EAAE,4BAA4B,EACvC,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,6BAA6B,CAAC,CAExC;IAED;;;;;;;;;OASG;IACG,YAAY,CAAC,YAAY,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAiBvE;YAIa,OAAO;IA+CrB,wFAAwF;YAC1E,oBAAoB;IA+ElC,0FAA0F;YAC5E,cAAc;IAmD5B;;;;;OAKG;YACW,aAAa;YA4Cb,YAAY;IAsC1B,OAAO,CAAC,OAAO;IAcf,OAAO,CAAC,QAAQ;YAcF,OAAO;YAkBP,mBAAmB;CAUlC"}
@@ -0,0 +1,346 @@
1
+ import { NotFoundError, ValidationError, assertFound, detectContractFormat, noopLogger, runBestEffort, summarizeContract, } from '@cat-factory/kernel';
2
+ import { normalizeDirPath, probeRepoSourceStatus, syncRepoSource, } from '../repoSourceSync/repo-source-sync.js';
3
+ import { SERVICE_MANIFEST_FILE, commonDirectory, contractIdFromPath, contractTitleFromPath, normalizeFilePath, parseServiceManifest, slugFromDirName, } from './foundational-source.logic.js';
4
+ /**
5
+ * Repo-sourced foundational services: link a repo FOLDER of service definitions (or an
6
+ * explicit FILE list for one service), resync it, and answer the cheap "check for changes"
7
+ * without writing. Reads go through the tier's existing GitHub installation — no new
8
+ * credential store.
9
+ *
10
+ * The shared repo-source engine (`repoSourceSync`) owns the mechanics that every repo-sourced
11
+ * library gets identically — pin the head commit BEFORE reading, run the reconcile, sweep
12
+ * tombstones by produced id, stamp the sync state, invalidate only on a real change. The
13
+ * differentiator supplied here is what a UNIT is, and there are two of them:
14
+ *
15
+ * - **`directory`**: one service per immediate subdirectory, identified by its `service.md`.
16
+ * - **`files`**: one service, named on the LINK, whose contracts are the linked file paths.
17
+ * There is no directory convention to read identity from, which is exactly why the link
18
+ * carries it (refused at the write boundary otherwise).
19
+ *
20
+ * Both modes anchor their staleness probe on ONE directory (`dirPath`), so a `files` source
21
+ * with a dozen linked files still costs a single commit read per freshness check.
22
+ */
23
+ export class FoundationalServiceSourceService {
24
+ deps;
25
+ log;
26
+ constructor(deps) {
27
+ this.deps = deps;
28
+ this.log = deps.logger ?? noopLogger;
29
+ }
30
+ /** Linked sources for a tier + their last-synced state. */
31
+ async list(ownerKind, ownerId) {
32
+ const rows = await this.deps.foundationalServiceSourceRepository.listByOwner(ownerKind, ownerId);
33
+ return rows.map(toWire);
34
+ }
35
+ /** Link a repo folder (or file list) as a source. Does not sync (call {@link sync}). */
36
+ async link(ownerKind, ownerId, input) {
37
+ const now = this.deps.clock.now();
38
+ const filePaths = (input.filePaths ?? []).map(normalizeFilePath).filter(Boolean);
39
+ // A `files` source anchors its probe on the linked files' deepest common directory, so
40
+ // one commit read covers the whole set (see the class note). A `directory` source anchors
41
+ // on the scanned subtree itself.
42
+ const dirPath = input.mode === 'files' ? commonDirectory(filePaths) : normalizeDirPath(input.dirPath);
43
+ const record = {
44
+ id: this.deps.idGenerator.next('fndsrc'),
45
+ ownerKind,
46
+ ownerId,
47
+ repoOwner: input.repoOwner.trim(),
48
+ repoName: input.repoName.trim(),
49
+ gitRef: input.gitRef?.trim() || 'HEAD',
50
+ mode: input.mode,
51
+ dirPath,
52
+ filePaths,
53
+ serviceId: input.mode === 'files' ? (input.serviceId ?? null) : null,
54
+ serviceName: input.mode === 'files' ? (input.serviceName ?? null) : null,
55
+ serviceSummary: input.mode === 'files' ? (input.serviceSummary ?? null) : null,
56
+ lastSyncedCommit: null,
57
+ lastSyncedAt: null,
58
+ createdAt: now,
59
+ deletedAt: null,
60
+ };
61
+ await this.deps.foundationalServiceSourceRepository.upsert(record);
62
+ return toWire(record);
63
+ }
64
+ /** Unlink a source and tombstone every service it produced. */
65
+ async unlink(ownerKind, ownerId, sourceId) {
66
+ const source = await this.require(ownerKind, ownerId, sourceId);
67
+ const now = this.deps.clock.now();
68
+ const services = await this.deps.foundationalServiceRepository.listBySource(sourceId);
69
+ if (services.length > 0) {
70
+ await this.deps.foundationalServiceRepository.softDeleteBySource(sourceId, now);
71
+ for (const service of services) {
72
+ await this.deps.apiContractRepository.deleteForService(service.ownerKind, service.ownerId, service.serviceId);
73
+ }
74
+ }
75
+ await this.deps.foundationalServiceSourceRepository.softDelete(source.id, now);
76
+ if (services.length > 0)
77
+ await this.deps.invalidateCatalog?.(ownerKind, ownerId);
78
+ }
79
+ /** The cheap "check for changes": one head-commit read compared to the pinned commit. */
80
+ async status(ownerKind, ownerId, sourceId) {
81
+ const source = await this.require(ownerKind, ownerId, sourceId);
82
+ const installationId = await this.requireInstallation(source);
83
+ return probeRepoSourceStatus({ source, installationId, githubClient: this.deps.githubClient });
84
+ }
85
+ /** Resync a source by id, resolving its owner from the stored row (the sweeper's entry point). */
86
+ async syncById(sourceId) {
87
+ const source = assertFound(await this.deps.foundationalServiceSourceRepository.get(sourceId), 'FoundationalServiceSource', sourceId);
88
+ if (source.deletedAt !== null)
89
+ throw new NotFoundError('FoundationalServiceSource', sourceId);
90
+ return this.runSync(source);
91
+ }
92
+ /** Resync a source: read the tree, upsert changed services, tombstone removed ones. */
93
+ async sync(ownerKind, ownerId, sourceId) {
94
+ return this.runSync(await this.require(ownerKind, ownerId, sourceId));
95
+ }
96
+ /**
97
+ * AUTOREFRESH: resync every source whose last sync is older than `staleAfterMs`, bounded to
98
+ * `limit` per pass. Driven by the periodic sweep on both facades (a Cloudflare cron tick, a
99
+ * Node interval), so a linked repo stays fresh whether or not anyone opens the management
100
+ * surface or starts a run.
101
+ *
102
+ * Best-effort PER SOURCE: one repo whose installation was revoked must not stop the pass from
103
+ * refreshing the rest, so each failure is logged with its cause and the sweep continues. The
104
+ * count returned is sources SYNCED, not sources attempted.
105
+ */
106
+ async refreshStale(staleAfterMs, limit) {
107
+ const now = this.deps.clock.now();
108
+ const stale = await this.deps.foundationalServiceSourceRepository.listStale(now - staleAfterMs, limit);
109
+ let synced = 0;
110
+ for (const source of stale) {
111
+ const result = await runBestEffort(this.log, 'foundationalServices.refreshSource', () => this.runSync(source), { sourceId: source.id, ownerKind: source.ownerKind, ownerId: source.ownerId });
112
+ if (result)
113
+ synced++;
114
+ }
115
+ return synced;
116
+ }
117
+ // --- internals ----------------------------------------------------------
118
+ async runSync(source) {
119
+ // Invariant across the whole sync — resolved ONCE, never per file.
120
+ const installationId = await this.requireInstallation(source);
121
+ return syncRepoSource({
122
+ source,
123
+ installationId,
124
+ githubClient: this.deps.githubClient,
125
+ now: this.deps.clock.now(),
126
+ listExisting: () => this.deps.foundationalServiceRepository.listBySource(source.id),
127
+ existingId: (s) => s.serviceId,
128
+ reconcile: async ({ readRef, commitMoved, now }, existing) => {
129
+ // The source dir's head commit is the exact staleness signal: unchanged ⇒ nothing under
130
+ // it moved (manifest OR contract document), so every service is unchanged and not a
131
+ // single per-directory read is paid for.
132
+ if (!commitMoved) {
133
+ return {
134
+ liveIds: new Set(existing.map((s) => s.serviceId)),
135
+ upserted: 0,
136
+ unchanged: existing.length,
137
+ };
138
+ }
139
+ return source.mode === 'files'
140
+ ? this.reconcileFiles(source, installationId, readRef, now, existing)
141
+ : this.reconcileDirectories(source, installationId, readRef, now, existing);
142
+ },
143
+ tombstone: async (service, now) => {
144
+ await this.deps.foundationalServiceRepository.softDelete(service.ownerKind, service.ownerId, service.serviceId, now);
145
+ await this.deps.apiContractRepository.deleteForService(service.ownerKind, service.ownerId, service.serviceId);
146
+ },
147
+ updateSyncState: (commit, now) => this.deps.foundationalServiceSourceRepository.updateSyncState(source.id, commit, now),
148
+ invalidate: () => this.deps.invalidateCatalog?.(source.ownerKind, source.ownerId) ?? Promise.resolve(),
149
+ });
150
+ }
151
+ /** `directory` mode: one service per immediate subdirectory carrying a `service.md`. */
152
+ async reconcileDirectories(source, installationId, readRef, now, existing) {
153
+ const existingById = new Map(existing.map((s) => [s.serviceId, s]));
154
+ // Sorted by name (code-unit order, locale-independent) so slug-collision resolution is
155
+ // deterministic — first wins — regardless of ICU collation.
156
+ const dirs = (await this.listDir(source, installationId, source.dirPath, readRef))
157
+ .filter((e) => e.type === 'dir')
158
+ .sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
159
+ const liveIds = new Set();
160
+ const seenSlugs = new Set();
161
+ let upserted = 0;
162
+ let incomplete = false;
163
+ for (const dir of dirs) {
164
+ const serviceId = slugFromDirName(dir.name);
165
+ // Two sibling dirs can slug to the same id (`file_storage` + `file-storage`). Keep the
166
+ // first deterministically rather than silently overwriting one service's row with
167
+ // another's.
168
+ if (seenSlugs.has(serviceId))
169
+ continue;
170
+ seenSlugs.add(serviceId);
171
+ const entries = await this.listDir(source, installationId, dir.path, readRef);
172
+ const manifestEntry = entries.find((e) => e.type === 'file' && e.name.toLowerCase() === SERVICE_MANIFEST_FILE);
173
+ // A directory with no manifest is not a service — skip it entirely rather than
174
+ // registering an unnamed one. It never enters `liveIds`, so a directory that LOSES its
175
+ // manifest retires the service it used to describe.
176
+ if (!manifestEntry)
177
+ continue;
178
+ const manifestFile = await this.readFile(source, installationId, manifestEntry.path, readRef);
179
+ const parsed = manifestFile ? parseServiceManifest(manifestFile.content) : null;
180
+ if (!parsed) {
181
+ // Unreadable/unparseable THIS round: keep any prior row alive rather than retiring a
182
+ // service over a transient read or an in-progress edit, and leave the pinned commit
183
+ // behind so the next sync re-reads it.
184
+ const prior = existingById.get(serviceId);
185
+ if (prior) {
186
+ liveIds.add(serviceId);
187
+ incomplete = true;
188
+ }
189
+ continue;
190
+ }
191
+ const contracts = await this.readContracts(source, installationId, readRef, serviceId, entries
192
+ .filter((e) => e.type === 'file' && e.path !== manifestEntry.path)
193
+ .map((e) => e.path), now);
194
+ await this.writeService(source, {
195
+ serviceId,
196
+ name: parsed.name,
197
+ summary: parsed.summary,
198
+ description: parsed.description,
199
+ capabilities: parsed.capabilities,
200
+ sourcePath: dir.path,
201
+ }, contracts, existingById.get(serviceId), readRef, now);
202
+ liveIds.add(serviceId);
203
+ upserted++;
204
+ }
205
+ // Every live service is rewritten on a moved commit, so nothing is reported unchanged —
206
+ // the cheap path is the `!commitMoved` short-circuit above, which is where an untouched
207
+ // source actually saves its reads.
208
+ return { liveIds, upserted, unchanged: 0, ...(incomplete ? { incomplete: true } : {}) };
209
+ }
210
+ /** `files` mode: ONE service, named on the link, whose contracts are the linked paths. */
211
+ async reconcileFiles(source, installationId, readRef, now, existing) {
212
+ const serviceId = source.serviceId;
213
+ // Guaranteed by the link-time `v.check`, but a stored row outlives the schema that wrote
214
+ // it — so this is a real guard, not a type narrowing convenience.
215
+ if (!serviceId || !source.serviceName) {
216
+ throw new ValidationError('This source does not name the service its files describe', {
217
+ reason: 'foundational_source_unnamed_service',
218
+ sourceId: source.id,
219
+ });
220
+ }
221
+ const prior = existing.find((s) => s.serviceId === serviceId);
222
+ const contracts = await this.readContracts(source, installationId, readRef, serviceId, source.filePaths, now);
223
+ if (contracts.length === 0) {
224
+ // Every linked file read back unusable. Keep a prior row alive and leave the pin behind
225
+ // so the next pass re-reads — the alternative is retiring a service that a momentary
226
+ // read failure made look deleted.
227
+ if (prior)
228
+ return { liveIds: new Set([serviceId]), upserted: 0, unchanged: 1, incomplete: true };
229
+ return { liveIds: new Set(), upserted: 0, unchanged: 0, incomplete: true };
230
+ }
231
+ await this.writeService(source, {
232
+ serviceId,
233
+ name: source.serviceName,
234
+ summary: source.serviceSummary ?? source.serviceName,
235
+ description: '',
236
+ capabilities: [],
237
+ sourcePath: source.dirPath,
238
+ }, contracts, prior, readRef, now);
239
+ return { liveIds: new Set([serviceId]), upserted: 1, unchanged: 0 };
240
+ }
241
+ /**
242
+ * Read the given repo paths and keep the ones that ARE contract documents. A file whose
243
+ * format is unrecognised is skipped rather than stored as an opaque blob: the format is what
244
+ * tells a downstream agent how to read it, and a "contract" nobody can interpret is worse in
245
+ * an agent's context than an absent one.
246
+ */
247
+ async readContracts(source, installationId, readRef, serviceId, paths, now) {
248
+ const out = [];
249
+ const seen = new Set();
250
+ for (const path of paths) {
251
+ const file = await this.readFile(source, installationId, path, readRef);
252
+ if (!file)
253
+ continue;
254
+ const format = detectContractFormat(path, file.content);
255
+ if (!format)
256
+ continue;
257
+ const contractId = contractIdFromPath(path);
258
+ if (seen.has(contractId))
259
+ continue;
260
+ seen.add(contractId);
261
+ const summary = summarizeContract({
262
+ contractId,
263
+ format,
264
+ title: contractTitleFromPath(path),
265
+ path,
266
+ body: file.content,
267
+ });
268
+ out.push({
269
+ ownerKind: source.ownerKind,
270
+ ownerId: source.ownerId,
271
+ serviceId,
272
+ contractId,
273
+ format,
274
+ title: summary.title,
275
+ body: file.content,
276
+ operations: summary.operations,
277
+ omittedOperations: summary.omittedOperations,
278
+ sourcePath: path,
279
+ sourceSha: file.sha,
280
+ createdAt: now,
281
+ updatedAt: now,
282
+ });
283
+ }
284
+ return out;
285
+ }
286
+ async writeService(source, identity, contracts, prior, readRef, now) {
287
+ await this.deps.foundationalServiceRepository.upsert({
288
+ serviceId: identity.serviceId,
289
+ ownerKind: source.ownerKind,
290
+ ownerId: source.ownerId,
291
+ name: identity.name,
292
+ summary: identity.summary,
293
+ description: identity.description,
294
+ capabilities: identity.capabilities,
295
+ sourceId: source.id,
296
+ sourcePath: identity.sourcePath,
297
+ pinnedCommit: readRef,
298
+ createdAt: prior?.createdAt ?? now,
299
+ updatedAt: now,
300
+ deletedAt: null,
301
+ });
302
+ await this.deps.apiContractRepository.replaceForService(source.ownerKind, source.ownerId, identity.serviceId, contracts);
303
+ }
304
+ listDir(source, installationId, path, readRef) {
305
+ return this.deps.githubClient.listDirectory(installationId, { owner: source.repoOwner, repo: source.repoName }, path, readRef);
306
+ }
307
+ readFile(source, installationId, path, readRef) {
308
+ return this.deps.githubClient.getFileContent(installationId, { owner: source.repoOwner, repo: source.repoName }, path, readRef);
309
+ }
310
+ async require(ownerKind, ownerId, sourceId) {
311
+ const source = assertFound(await this.deps.foundationalServiceSourceRepository.get(sourceId), 'FoundationalServiceSource', sourceId);
312
+ // The route gates only authorize the addressed owner prefix, so the record must belong to
313
+ // that owner; a 404 hides other tenants' sources entirely.
314
+ if (source.ownerKind !== ownerKind || source.ownerId !== ownerId || source.deletedAt !== null) {
315
+ throw new NotFoundError('FoundationalServiceSource', sourceId);
316
+ }
317
+ return source;
318
+ }
319
+ async requireInstallation(source) {
320
+ const installationId = await this.deps.resolveInstallationId(source.ownerKind, source.ownerId);
321
+ if (installationId === null) {
322
+ throw new ValidationError('No GitHub installation is available for this scope; connect GitHub before syncing a source', { reason: 'foundational_source_no_installation', sourceId: source.id });
323
+ }
324
+ return installationId;
325
+ }
326
+ }
327
+ function toWire(record) {
328
+ return {
329
+ id: record.id,
330
+ ownerKind: record.ownerKind,
331
+ ownerId: record.ownerId,
332
+ repoOwner: record.repoOwner,
333
+ repoName: record.repoName,
334
+ gitRef: record.gitRef,
335
+ mode: record.mode,
336
+ dirPath: record.dirPath,
337
+ filePaths: record.filePaths,
338
+ serviceId: record.serviceId,
339
+ serviceName: record.serviceName,
340
+ serviceSummary: record.serviceSummary,
341
+ lastSyncedCommit: record.lastSyncedCommit,
342
+ lastSyncedAt: record.lastSyncedAt,
343
+ createdAt: record.createdAt,
344
+ };
345
+ }
346
+ //# sourceMappingURL=FoundationalServiceSourceService.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FoundationalServiceSourceService.js","sourceRoot":"","sources":["../../src/foundationalServices/FoundationalServiceSourceService.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ import type { UploadApiContract } from '@cat-factory/contracts';
2
+ /**
3
+ * Refuse an uploaded contract whose body does not match its declared format. Throws a
4
+ * `ValidationError` carrying a machine-readable `reason` the SPA maps to translated copy;
5
+ * returns silently when the document is acceptable.
6
+ */
7
+ export declare function validateUploadedContract(upload: UploadApiContract): void;
8
+ //# sourceMappingURL=contract-validation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contract-validation.d.ts","sourceRoot":"","sources":["../../src/foundationalServices/contract-validation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAqB,iBAAiB,EAAE,MAAM,wBAAwB,CAAA;AAyBlF;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,iBAAiB,GAAG,IAAI,CAqBxE"}
@@ -0,0 +1,43 @@
1
+ import { ValidationError, isOpenApiDocument } from '@cat-factory/kernel';
2
+ // ---------------------------------------------------------------------------
3
+ // Write-boundary validation for an UPLOADED contract document.
4
+ //
5
+ // The point is to refuse a document that will read as garbage to a downstream agent at the
6
+ // moment someone uploads it, rather than at the moment a coder is handed it. The check is
7
+ // per-format and deliberately shallow:
8
+ //
9
+ // - `openapi` is PARSED — a document claiming to be OpenAPI but which is not is the failure
10
+ // mode that produces a service whose catalog entry lists zero operations while looking
11
+ // perfectly registered.
12
+ // - the two TypeScript formats are checked only for the LIBRARY IMPORT they claim. They
13
+ // cannot be evaluated (no code generation in a Worker isolate, and executing an uploaded
14
+ // module would be a remote-code-execution hole), so the honest check is that the document
15
+ // is what its declared format says it is; the agent reads the source itself.
16
+ // ---------------------------------------------------------------------------
17
+ /** The package import each TypeScript contract format is recognised by. */
18
+ const REQUIRED_IMPORT = {
19
+ 'toad-contract': { package: '@toad-contracts/', label: '@toad-contracts/core' },
20
+ 'lokalise-api-contract': { package: '@lokalise/api-contract', label: '@lokalise/api-contract' },
21
+ };
22
+ /**
23
+ * Refuse an uploaded contract whose body does not match its declared format. Throws a
24
+ * `ValidationError` carrying a machine-readable `reason` the SPA maps to translated copy;
25
+ * returns silently when the document is acceptable.
26
+ */
27
+ export function validateUploadedContract(upload) {
28
+ if (upload.format === 'openapi') {
29
+ if (!isOpenApiDocument(upload.body)) {
30
+ throw new ValidationError(`Contract '${upload.contractId}' is not a valid OpenAPI 3.x document (JSON or YAML)`, { reason: 'invalid_openapi_document', contractId: upload.contractId });
31
+ }
32
+ return;
33
+ }
34
+ const required = REQUIRED_IMPORT[upload.format];
35
+ if (required && !upload.body.includes(required.package)) {
36
+ throw new ValidationError(`Contract '${upload.contractId}' does not reference ${required.label}`, {
37
+ reason: 'contract_library_not_referenced',
38
+ contractId: upload.contractId,
39
+ expected: required.label,
40
+ });
41
+ }
42
+ }
43
+ //# sourceMappingURL=contract-validation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contract-validation.js","sourceRoot":"","sources":["../../src/foundationalServices/contract-validation.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAA;AAExE,8EAA8E;AAC9E,+DAA+D;AAC/D,EAAE;AACF,2FAA2F;AAC3F,0FAA0F;AAC1F,uCAAuC;AACvC,EAAE;AACF,8FAA8F;AAC9F,2FAA2F;AAC3F,4BAA4B;AAC5B,0FAA0F;AAC1F,6FAA6F;AAC7F,8FAA8F;AAC9F,iFAAiF;AACjF,8EAA8E;AAE9E,2EAA2E;AAC3E,MAAM,eAAe,GAA2E;IAC9F,eAAe,EAAE,EAAE,OAAO,EAAE,kBAAkB,EAAE,KAAK,EAAE,sBAAsB,EAAE;IAC/E,uBAAuB,EAAE,EAAE,OAAO,EAAE,wBAAwB,EAAE,KAAK,EAAE,wBAAwB,EAAE;CAChG,CAAA;AAED;;;;GAIG;AACH,MAAM,UAAU,wBAAwB,CAAC,MAAyB;IAChE,IAAI,MAAM,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;QAChC,IAAI,CAAC,iBAAiB,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC;YACpC,MAAM,IAAI,eAAe,CACvB,aAAa,MAAM,CAAC,UAAU,sDAAsD,EACpF,EAAE,MAAM,EAAE,0BAA0B,EAAE,UAAU,EAAE,MAAM,CAAC,UAAU,EAAE,CACtE,CAAA;QACH,CAAC;QACD,OAAM;IACR,CAAC;IACD,MAAM,QAAQ,GAAG,eAAe,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;IAC/C,IAAI,QAAQ,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;QACxD,MAAM,IAAI,eAAe,CACvB,aAAa,MAAM,CAAC,UAAU,wBAAwB,QAAQ,CAAC,KAAK,EAAE,EACtE;YACE,MAAM,EAAE,iCAAiC;YACzC,UAAU,EAAE,MAAM,CAAC,UAAU;YAC7B,QAAQ,EAAE,QAAQ,CAAC,KAAK;SACzB,CACF,CAAA;IACH,CAAC;AACH,CAAC"}
@@ -0,0 +1,22 @@
1
+ import type { FoundationalService, ResolvedFoundationalService } from '@cat-factory/contracts';
2
+ import type { ApiContractManifestEntry, FoundationalServiceRecord } from '@cat-factory/kernel';
3
+ /** A stored record + its contract manifest → the wire shape. */
4
+ export declare function toWire(record: FoundationalServiceRecord, manifest: ApiContractManifestEntry[]): FoundationalService;
5
+ export interface MergeFoundationalTiersInput {
6
+ accountRows: FoundationalServiceRecord[];
7
+ /** Read `includeDeleted` — a workspace tombstone is what suppresses an inherited service. */
8
+ workspaceRows: FoundationalServiceRecord[];
9
+ accountManifest: Map<string, ApiContractManifestEntry[]>;
10
+ workspaceManifest: Map<string, ApiContractManifestEntry[]>;
11
+ }
12
+ /**
13
+ * Merge the account and workspace tiers into the catalog an agent sees. The workspace tier
14
+ * wins by id, and a workspace TOMBSTONE removes the id entirely — including an account service
15
+ * the workspace never authored, which is the whole point of reading the workspace tier with
16
+ * tombstones included.
17
+ *
18
+ * Sorted by id so a catalog rendered into a prompt is byte-stable across resolves; an unstable
19
+ * order would break prompt caching for every design dispatch in the workspace.
20
+ */
21
+ export declare function mergeFoundationalTiers(input: MergeFoundationalTiersInput): ResolvedFoundationalService[];
22
+ //# sourceMappingURL=foundational-catalog.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"foundational-catalog.d.ts","sourceRoot":"","sources":["../../src/foundationalServices/foundational-catalog.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,mBAAmB,EAEnB,2BAA2B,EAC5B,MAAM,wBAAwB,CAAA;AAC/B,OAAO,KAAK,EAAE,wBAAwB,EAAE,yBAAyB,EAAE,MAAM,qBAAqB,CAAA;AAM9F,gEAAgE;AAChE,wBAAgB,MAAM,CACpB,MAAM,EAAE,yBAAyB,EACjC,QAAQ,EAAE,wBAAwB,EAAE,GACnC,mBAAmB,CAyBrB;AAED,MAAM,WAAW,2BAA2B;IAC1C,WAAW,EAAE,yBAAyB,EAAE,CAAA;IACxC,6FAA6F;IAC7F,aAAa,EAAE,yBAAyB,EAAE,CAAA;IAC1C,eAAe,EAAE,GAAG,CAAC,MAAM,EAAE,wBAAwB,EAAE,CAAC,CAAA;IACxD,iBAAiB,EAAE,GAAG,CAAC,MAAM,EAAE,wBAAwB,EAAE,CAAC,CAAA;CAC3D;AAED;;;;;;;;GAQG;AACH,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,2BAA2B,GACjC,2BAA2B,EAAE,CAW/B"}
@@ -0,0 +1,58 @@
1
+ // Pure tier-merge for the foundational-services catalog. No I/O — the service hands in the
2
+ // two tiers' rows plus their contract manifests, so every precedence rule here is
3
+ // unit-testable and identical on both runtimes.
4
+ /** A stored record + its contract manifest → the wire shape. */
5
+ export function toWire(record, manifest) {
6
+ return {
7
+ id: record.serviceId,
8
+ ownerKind: record.ownerKind,
9
+ name: record.name,
10
+ summary: record.summary,
11
+ description: record.description,
12
+ capabilities: record.capabilities,
13
+ contracts: manifest.map((entry) => ({
14
+ contractId: entry.contractId,
15
+ format: entry.format,
16
+ title: entry.title,
17
+ size: entry.size,
18
+ path: entry.sourcePath,
19
+ // Indexed once at WRITE time and stored on the row, so this projection shows an agent
20
+ // what the interface offers without loading a single document body.
21
+ operations: entry.operations,
22
+ omittedOperations: entry.omittedOperations,
23
+ })),
24
+ sourceId: record.sourceId,
25
+ sourcePath: record.sourcePath,
26
+ pinnedCommit: record.pinnedCommit,
27
+ createdAt: record.createdAt,
28
+ updatedAt: record.updatedAt,
29
+ };
30
+ }
31
+ /**
32
+ * Merge the account and workspace tiers into the catalog an agent sees. The workspace tier
33
+ * wins by id, and a workspace TOMBSTONE removes the id entirely — including an account service
34
+ * the workspace never authored, which is the whole point of reading the workspace tier with
35
+ * tombstones included.
36
+ *
37
+ * Sorted by id so a catalog rendered into a prompt is byte-stable across resolves; an unstable
38
+ * order would break prompt caching for every design dispatch in the workspace.
39
+ */
40
+ export function mergeFoundationalTiers(input) {
41
+ const merged = new Map();
42
+ for (const row of input.accountRows) {
43
+ if (row.deletedAt)
44
+ continue;
45
+ merged.set(row.serviceId, entry(row, input.accountManifest, 'account'));
46
+ }
47
+ for (const row of input.workspaceRows) {
48
+ if (row.deletedAt)
49
+ merged.delete(row.serviceId);
50
+ else
51
+ merged.set(row.serviceId, entry(row, input.workspaceManifest, 'workspace'));
52
+ }
53
+ return [...merged.values()].sort((a, b) => (a.id < b.id ? -1 : a.id > b.id ? 1 : 0));
54
+ }
55
+ function entry(record, manifest, tier) {
56
+ return { ...toWire(record, manifest.get(record.serviceId) ?? []), tier };
57
+ }
58
+ //# sourceMappingURL=foundational-catalog.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"foundational-catalog.js","sourceRoot":"","sources":["../../src/foundationalServices/foundational-catalog.ts"],"names":[],"mappings":"AAOA,2FAA2F;AAC3F,kFAAkF;AAClF,gDAAgD;AAEhD,gEAAgE;AAChE,MAAM,UAAU,MAAM,CACpB,MAAiC,EACjC,QAAoC;IAEpC,OAAO;QACL,EAAE,EAAE,MAAM,CAAC,SAAS;QACpB,SAAS,EAAE,MAAM,CAAC,SAAS;QAC3B,IAAI,EAAE,MAAM,CAAC,IAAI;QACjB,OAAO,EAAE,MAAM,CAAC,OAAO;QACvB,WAAW,EAAE,MAAM,CAAC,WAAW;QAC/B,YAAY,EAAE,MAAM,CAAC,YAAY;QACjC,SAAS,EAAE,QAAQ,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;YAClC,UAAU,EAAE,KAAK,CAAC,UAAU;YAC5B,MAAM,EAAE,KAAK,CAAC,MAAM;YACpB,KAAK,EAAE,KAAK,CAAC,KAAK;YAClB,IAAI,EAAE,KAAK,CAAC,IAAI;YAChB,IAAI,EAAE,KAAK,CAAC,UAAU;YACtB,sFAAsF;YACtF,oEAAoE;YACpE,UAAU,EAAE,KAAK,CAAC,UAAU;YAC5B,iBAAiB,EAAE,KAAK,CAAC,iBAAiB;SAC3C,CAAC,CAAC;QACH,QAAQ,EAAE,MAAM,CAAC,QAAQ;QACzB,UAAU,EAAE,MAAM,CAAC,UAAU;QAC7B,YAAY,EAAE,MAAM,CAAC,YAAY;QACjC,SAAS,EAAE,MAAM,CAAC,SAAS;QAC3B,SAAS,EAAE,MAAM,CAAC,SAAS;KAC5B,CAAA;AACH,CAAC;AAUD;;;;;;;;GAQG;AACH,MAAM,UAAU,sBAAsB,CACpC,KAAkC;IAElC,MAAM,MAAM,GAAG,IAAI,GAAG,EAAuC,CAAA;IAC7D,KAAK,MAAM,GAAG,IAAI,KAAK,CAAC,WAAW,EAAE,CAAC;QACpC,IAAI,GAAG,CAAC,SAAS;YAAE,SAAQ;QAC3B,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,SAAS,EAAE,KAAK,CAAC,GAAG,EAAE,KAAK,CAAC,eAAe,EAAE,SAAS,CAAC,CAAC,CAAA;IACzE,CAAC;IACD,KAAK,MAAM,GAAG,IAAI,KAAK,CAAC,aAAa,EAAE,CAAC;QACtC,IAAI,GAAG,CAAC,SAAS;YAAE,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,CAAA;;YAC1C,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,SAAS,EAAE,KAAK,CAAC,GAAG,EAAE,KAAK,CAAC,iBAAiB,EAAE,WAAW,CAAC,CAAC,CAAA;IAClF,CAAC;IACD,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;AACtF,CAAC;AAED,SAAS,KAAK,CACZ,MAAiC,EACjC,QAAiD,EACjD,IAA6B;IAE7B,OAAO,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC,EAAE,IAAI,EAAE,CAAA;AAC1E,CAAC"}