@cat-factory/agents 0.94.0 → 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 (45) hide show
  1. package/dist/agents/kinds/registry.d.ts +25 -0
  2. package/dist/agents/kinds/registry.d.ts.map +1 -1
  3. package/dist/agents/kinds/registry.js +39 -0
  4. package/dist/agents/kinds/registry.js.map +1 -1
  5. package/dist/agents/kinds/traits.d.ts +22 -0
  6. package/dist/agents/kinds/traits.d.ts.map +1 -1
  7. package/dist/agents/kinds/traits.js +42 -3
  8. package/dist/agents/kinds/traits.js.map +1 -1
  9. package/dist/agents/kinds/variants.d.ts +115 -0
  10. package/dist/agents/kinds/variants.d.ts.map +1 -0
  11. package/dist/agents/kinds/variants.js +35 -0
  12. package/dist/agents/kinds/variants.js.map +1 -0
  13. package/dist/agents/prompts/bespoke-kinds.d.ts +72 -0
  14. package/dist/agents/prompts/bespoke-kinds.d.ts.map +1 -0
  15. package/dist/agents/prompts/bespoke-kinds.js +114 -0
  16. package/dist/agents/prompts/bespoke-kinds.js.map +1 -0
  17. package/dist/foundationalServices/FoundationalServiceCatalogService.d.ts +65 -0
  18. package/dist/foundationalServices/FoundationalServiceCatalogService.d.ts.map +1 -0
  19. package/dist/foundationalServices/FoundationalServiceCatalogService.js +284 -0
  20. package/dist/foundationalServices/FoundationalServiceCatalogService.js.map +1 -0
  21. package/dist/foundationalServices/FoundationalServiceRunResolver.d.ts +37 -0
  22. package/dist/foundationalServices/FoundationalServiceRunResolver.d.ts.map +1 -0
  23. package/dist/foundationalServices/FoundationalServiceRunResolver.js +92 -0
  24. package/dist/foundationalServices/FoundationalServiceRunResolver.js.map +1 -0
  25. package/dist/foundationalServices/FoundationalServiceSourceService.d.ts +86 -0
  26. package/dist/foundationalServices/FoundationalServiceSourceService.d.ts.map +1 -0
  27. package/dist/foundationalServices/FoundationalServiceSourceService.js +346 -0
  28. package/dist/foundationalServices/FoundationalServiceSourceService.js.map +1 -0
  29. package/dist/foundationalServices/contract-validation.d.ts +8 -0
  30. package/dist/foundationalServices/contract-validation.d.ts.map +1 -0
  31. package/dist/foundationalServices/contract-validation.js +43 -0
  32. package/dist/foundationalServices/contract-validation.js.map +1 -0
  33. package/dist/foundationalServices/foundational-catalog.d.ts +22 -0
  34. package/dist/foundationalServices/foundational-catalog.d.ts.map +1 -0
  35. package/dist/foundationalServices/foundational-catalog.js +58 -0
  36. package/dist/foundationalServices/foundational-catalog.js.map +1 -0
  37. package/dist/foundationalServices/foundational-source.logic.d.ts +51 -0
  38. package/dist/foundationalServices/foundational-source.logic.d.ts.map +1 -0
  39. package/dist/foundationalServices/foundational-source.logic.js +109 -0
  40. package/dist/foundationalServices/foundational-source.logic.js.map +1 -0
  41. package/dist/index.d.ts +9 -1
  42. package/dist/index.d.ts.map +1 -1
  43. package/dist/index.js +15 -1
  44. package/dist/index.js.map +1 -1
  45. package/package.json +5 -5
@@ -0,0 +1,114 @@
1
+ import { baseSystemPromptFor } from '../catalog.js';
2
+ import { INLINE_ENGINE_SYSTEM_PROMPTS } from './inline-engine.js';
3
+ import { FINAL_ANSWER_IN_REPLY } from './shared.js';
4
+ // The two bespoke CONTAINER prompts (`merger`, `on-call`) and the map that collects every kind
5
+ // whose prompt is a bespoke constant rather than a `systemPromptFor` composition.
6
+ //
7
+ // Each is declared as a ROLE half and a DIRECTIVES half rather than one string, because a
8
+ // workspace can replace the role from the pipeline builder (and a registered agent-kind VARIANT
9
+ // can replace it in code — see ../kinds/variants.ts). The directives half is what
10
+ // `applySurfaceDirectives` is for every other kind — the read-only guardrail, the machine-parsed
11
+ // output contract, and the answer-in-your-reply rule — so it must survive a replacement rather
12
+ // than be editable prose inside it. Each `*_DIRECTIVES` string CARRIES ITS OWN LEADING SEPARATOR,
13
+ // exactly as the appended directives do on the `systemPromptFor` path (where they are recovered
14
+ // by slicing the base off), so `role + directives` is the shipped prompt byte for byte.
15
+ // `promptOverrides.spec.ts` pins that.
16
+ //
17
+ // This lives in `@cat-factory/agents` rather than beside the container executor that dispatches
18
+ // the two kinds because the ENGINE needs the same answer: it resolves a variant's prompt against
19
+ // the SHIPPED base once per dispatch, and a bespoke kind's shipped base is its role half, not
20
+ // what `baseSystemPromptFor` would return. With the map in the HTTP layer the engine could only
21
+ // have guessed, and would have folded a variant's addition onto the wrong text for exactly the
22
+ // two kinds whose prompts carry a parsed output contract.
23
+ /**
24
+ * The EDITABLE half of the `merger` prompt: what the agent is for. Replaced wholesale by a
25
+ * workspace override, so it must carry no invariant the engine depends on.
26
+ */
27
+ export const MERGER_ROLE_PROMPT = 'You are a release manager assessing a pull request before merge. Inspect the ' +
28
+ 'change (the diff between the pull-request branch(es) and their base) and judge three axes, ' +
29
+ 'each as a number from 0 (trivial/safe) to 1 (severe): complexity (how intricate the ' +
30
+ 'change is), risk (how likely it is to break something), and impact (blast radius ' +
31
+ 'if it does). When the change spans several repositories, weigh the COMBINED cross-repo ' +
32
+ 'change as one and return a single assessment. Be conservative.';
33
+ /**
34
+ * The NON-EDITABLE half: the JSON contract `resolveMergerStep` parses, and the rule that keeps a
35
+ * reasoning model's answer out of its hidden channel. Re-appended on top of an override — an
36
+ * override that could drop these fails the run in exactly the ways they exist to prevent (an
37
+ * empty visible reply the harness reads as unusable; a merge decision with nothing to parse).
38
+ */
39
+ export const MERGER_DIRECTIVES = ' Respond with ONLY a JSON ' +
40
+ 'object of shape {"complexity":0.0,"risk":0.0,"impact":0.0,"rationale":"…"} — no prose, ' +
41
+ 'no code fences. ' +
42
+ FINAL_ANSWER_IN_REPLY;
43
+ /** Role prompt the `merger` step runs under (scores the PR; returns JSON only). */
44
+ export const MERGER_SYSTEM_PROMPT = MERGER_ROLE_PROMPT + MERGER_DIRECTIVES;
45
+ /**
46
+ * The EDITABLE half of the `on-call` prompt: what the agent is investigating and how to weigh
47
+ * it. Replaced wholesale by a workspace override — note that the read-only guardrail is
48
+ * deliberately NOT here, since an on-call agent that could be told to commit is the specific
49
+ * accident this split prevents.
50
+ */
51
+ export const ON_CALL_ROLE_PROMPT = 'You are an on-call engineer investigating a possible post-release regression. A ' +
52
+ 'recently merged pull request shipped, and the evidence below (alerting Datadog ' +
53
+ 'monitors/SLOs and recent error logs) suggests the service regressed afterward. Read ' +
54
+ 'the PR diff on the head branch and weigh whether THIS change is the likely cause — ' +
55
+ 'beware correlation vs causation; a coincident deploy is not proof.';
56
+ /**
57
+ * The NON-EDITABLE half: the read-only guardrail (this kind investigates, a human decides
58
+ * whether to revert — see `resolveOnCallStep`), the JSON contract the engine parses, and the
59
+ * answer-in-your-reply rule. Re-appended on top of an override.
60
+ */
61
+ export const ON_CALL_DIRECTIVES = ' You may read and ' +
62
+ 'inspect any file, but you MUST NOT modify, commit or revert anything; a human decides ' +
63
+ 'whether to revert. Respond with ONLY a JSON object of shape ' +
64
+ '{"culpritConfidence":0.0,"recommendation":"revert"|"hold"|"monitor","rationale":"…",' +
65
+ '"evidence":["…"]} — no prose, no code fences. ' +
66
+ FINAL_ANSWER_IN_REPLY;
67
+ /** Role prompt the `on-call` step runs under (investigates a regression; returns JSON only). */
68
+ export const ON_CALL_SYSTEM_PROMPT = ON_CALL_ROLE_PROMPT + ON_CALL_DIRECTIVES;
69
+ /**
70
+ * Every kind whose prompt is a bespoke constant rather than a `systemPromptFor` composition,
71
+ * keyed by agent kind and SPLIT at the boundary a replacement may cross
72
+ * ({@link BespokeSystemPrompt}).
73
+ *
74
+ * Two families, for the same reason. The container kinds `merger` and `on-call` dispatch a
75
+ * bespoke constant (both return a strict JSON assessment whose contract is stated in that
76
+ * prompt); the inline ENGINE steps — the requirements + clarity reviewers, both brainstorm
77
+ * stages, their rework editors and the Requirement Writer — are driven by
78
+ * `IterativeReviewService` as bare inline calls. Neither family passes through `systemPromptFor`,
79
+ * so neither gets its override applied or its invariants re-appended by that seam.
80
+ *
81
+ * Collected here so the prompt EDITOR, the RUN and a code-registered VARIANT agree on what "the
82
+ * built-in prompt for this kind" is. With the constants only inlined, an editor built on
83
+ * `systemPromptFor` showed the merger's thin one-line role — and the requirements reviewer's
84
+ * `roles.ts` line — as the baseline while something else entirely ran: "restore the built-in"
85
+ * restored a prompt that was never running, and a diff against the baseline was noise.
86
+ *
87
+ * Adding another such kind means adding it here, SPLIT — a kind added with its directives inside
88
+ * `role` compiles and runs fine, and fails only later, as a workspace that edited it loses its
89
+ * guardrail or its JSON contract.
90
+ *
91
+ * The two container kind ids are bare literals: their canonical constants live in kernel
92
+ * (`ON_CALL_AGENT_KIND`) and orchestration (`MERGER_AGENT_KIND`), and this package sits below
93
+ * both. Pinned by `promptOverrides.spec.ts`, which resolves them through those constants.
94
+ */
95
+ export const BESPOKE_SYSTEM_PROMPTS = {
96
+ ...INLINE_ENGINE_SYSTEM_PROMPTS,
97
+ merger: { role: MERGER_ROLE_PROMPT, directives: MERGER_DIRECTIVES },
98
+ 'on-call': { role: ON_CALL_ROLE_PROMPT, directives: ON_CALL_DIRECTIVES },
99
+ };
100
+ /**
101
+ * The SHIPPED prompt for a kind — the unit a per-workspace override or a registered variant
102
+ * REPLACES, and what the prompt editor shows as the baseline to diff against and restore to.
103
+ *
104
+ * It excludes the surface directives and trait guidance `systemPromptFor` layers on (and, for a
105
+ * bespoke kind, its `directives` half), because those are invariants of how the platform runs
106
+ * the kind (a read-only kind must not edit; a reasoning kind's answer must land in its visible
107
+ * reply) rather than editorial content. They are re-applied on top of a replacement, so handing
108
+ * them to an editor would only let someone delete something that comes back anyway — or, worse,
109
+ * duplicate it on save.
110
+ */
111
+ export function shippedBasePromptFor(kind, registry) {
112
+ return BESPOKE_SYSTEM_PROMPTS[kind]?.role ?? baseSystemPromptFor(kind, registry);
113
+ }
114
+ //# sourceMappingURL=bespoke-kinds.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bespoke-kinds.js","sourceRoot":"","sources":["../../../src/agents/prompts/bespoke-kinds.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAA;AAEnD,OAAO,EAAE,4BAA4B,EAAE,MAAM,oBAAoB,CAAA;AACjE,OAAO,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAA;AAEnD,+FAA+F;AAC/F,kFAAkF;AAClF,EAAE;AACF,0FAA0F;AAC1F,gGAAgG;AAChG,kFAAkF;AAClF,iGAAiG;AACjG,+FAA+F;AAC/F,kGAAkG;AAClG,gGAAgG;AAChG,wFAAwF;AACxF,uCAAuC;AACvC,EAAE;AACF,gGAAgG;AAChG,iGAAiG;AACjG,8FAA8F;AAC9F,gGAAgG;AAChG,+FAA+F;AAC/F,0DAA0D;AAE1D;;;GAGG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAC7B,+EAA+E;IAC/E,6FAA6F;IAC7F,sFAAsF;IACtF,mFAAmF;IACnF,yFAAyF;IACzF,gEAAgE,CAAA;AAElE;;;;;GAKG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAC5B,4BAA4B;IAC5B,yFAAyF;IACzF,kBAAkB;IAClB,qBAAqB,CAAA;AAEvB,mFAAmF;AACnF,MAAM,CAAC,MAAM,oBAAoB,GAAG,kBAAkB,GAAG,iBAAiB,CAAA;AAE1E;;;;;GAKG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAC9B,kFAAkF;IAClF,iFAAiF;IACjF,sFAAsF;IACtF,qFAAqF;IACrF,oEAAoE,CAAA;AAEtE;;;;GAIG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAC7B,oBAAoB;IACpB,wFAAwF;IACxF,8DAA8D;IAC9D,sFAAsF;IACtF,gDAAgD;IAChD,qBAAqB,CAAA;AAEvB,gGAAgG;AAChG,MAAM,CAAC,MAAM,qBAAqB,GAAG,mBAAmB,GAAG,kBAAkB,CAAA;AAE7E;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAoD;IACrF,GAAG,4BAA4B;IAC/B,MAAM,EAAE,EAAE,IAAI,EAAE,kBAAkB,EAAE,UAAU,EAAE,iBAAiB,EAAE;IACnE,SAAS,EAAE,EAAE,IAAI,EAAE,mBAAmB,EAAE,UAAU,EAAE,kBAAkB,EAAE;CACzE,CAAA;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,oBAAoB,CAAC,IAAe,EAAE,QAA2B;IAC/E,OAAO,sBAAsB,CAAC,IAAI,CAAC,EAAE,IAAI,IAAI,mBAAmB,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAA;AAClF,CAAC"}
@@ -0,0 +1,65 @@
1
+ import type { ApiContractDocument, CreateFoundationalServiceInput, FoundationalService, FoundationalServiceOwnerKind, ResolvedFoundationalService, UpdateFoundationalServiceInput } from '@cat-factory/contracts';
2
+ import type { ApiContractRepository, Clock, FoundationalServiceRepository, GroupCacheHandle, WorkspaceRepository } from '@cat-factory/kernel';
3
+ export interface FoundationalServiceCatalogDependencies {
4
+ foundationalServiceRepository: FoundationalServiceRepository;
5
+ apiContractRepository: ApiContractRepository;
6
+ workspaceRepository: WorkspaceRepository;
7
+ clock: Clock;
8
+ /** The merged per-workspace catalog cache. Absent (tests) ⇒ every resolve reads through. */
9
+ catalogCache?: GroupCacheHandle<ResolvedFoundationalService[]>;
10
+ }
11
+ /**
12
+ * The foundational-services catalog (docs/initiatives/foundational-services.md): per-tier CRUD
13
+ * plus the merged account ⊕ workspace resolve every design dispatch reads.
14
+ *
15
+ * Two properties are load-bearing and are why this is a service rather than a repository call:
16
+ *
17
+ * - **The catalog never carries a contract BODY.** `listTier` and `resolve` join the contract
18
+ * MANIFEST (id, format, title, byte size, operation names) onto each service in ONE extra
19
+ * query for the whole tier. The documents are read only by {@link contractsFor}, and only for
20
+ * the ids a caller asks for.
21
+ * - **A workspace row wins over an account row of the same id, and a workspace TOMBSTONE
22
+ * suppresses one.** That is what lets a board opt out of an org-wide service without an
23
+ * account admin, and it is why the merge reads the workspace tier `includeDeleted`.
24
+ */
25
+ export declare class FoundationalServiceCatalogService {
26
+ private readonly deps;
27
+ constructor(deps: FoundationalServiceCatalogDependencies);
28
+ /** One tier's registered services, contract manifests joined on. Raw — not merged. */
29
+ listTier(ownerKind: FoundationalServiceOwnerKind, ownerId: string): Promise<FoundationalService[]>;
30
+ /** Register a service at a tier. */
31
+ create(ownerKind: FoundationalServiceOwnerKind, ownerId: string, input: CreateFoundationalServiceInput): Promise<FoundationalService>;
32
+ /** Patch a registered service. `contracts`, when present, replaces the whole set. */
33
+ update(ownerKind: FoundationalServiceOwnerKind, ownerId: string, serviceId: string, input: UpdateFoundationalServiceInput): Promise<FoundationalService>;
34
+ /**
35
+ * Remove a service from a tier. A WORKSPACE removal writes a tombstone (which is also how a
36
+ * board suppresses an inherited account service); an ACCOUNT removal drops the row and its
37
+ * documents outright, since there is no higher tier for it to shadow.
38
+ */
39
+ remove(ownerKind: FoundationalServiceOwnerKind, ownerId: string, serviceId: string): Promise<void>;
40
+ /**
41
+ * Suppress an ACCOUNT service for one workspace by writing a workspace-tier tombstone. The
42
+ * row carries no content of its own — it exists only to lose the merge — so it is written
43
+ * here rather than through {@link create}, which would demand a name and summary for
44
+ * something that is never rendered.
45
+ */
46
+ suppressForWorkspace(workspaceId: string, serviceId: string): Promise<void>;
47
+ /** The merged catalog a workspace's agents see, cached per workspace. */
48
+ resolve(workspaceId: string): Promise<ResolvedFoundationalService[]>;
49
+ /**
50
+ * The LAZY contract read: full documents for the named services, resolved against the same
51
+ * account ⊕ workspace precedence the catalog uses. One batched query per tier — never a
52
+ * per-service read — and an empty `serviceIds` returns `[]` without touching the store.
53
+ *
54
+ * Precedence matters here as much as in the catalog: a workspace that overrode a service
55
+ * must hand out ITS documents, or the design would be reviewed against the org's spec while
56
+ * the code is written against the board's.
57
+ */
58
+ contractsFor(workspaceId: string, serviceIds: string[]): Promise<Map<string, ApiContractDocument[]>>;
59
+ /** Drop a tier's cached catalog. An account write invalidates every workspace's. */
60
+ invalidate(ownerKind: FoundationalServiceOwnerKind, ownerId: string): Promise<void>;
61
+ private loadCatalog;
62
+ private require;
63
+ private buildUploadedContracts;
64
+ }
65
+ //# sourceMappingURL=FoundationalServiceCatalogService.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FoundationalServiceCatalogService.d.ts","sourceRoot":"","sources":["../../src/foundationalServices/FoundationalServiceCatalogService.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,mBAAmB,EACnB,8BAA8B,EAC9B,mBAAmB,EACnB,4BAA4B,EAC5B,2BAA2B,EAC3B,8BAA8B,EAC/B,MAAM,wBAAwB,CAAA;AAC/B,OAAO,KAAK,EAGV,qBAAqB,EACrB,KAAK,EAEL,6BAA6B,EAC7B,gBAAgB,EAChB,mBAAmB,EACpB,MAAM,qBAAqB,CAAA;AAU5B,MAAM,WAAW,sCAAsC;IACrD,6BAA6B,EAAE,6BAA6B,CAAA;IAC5D,qBAAqB,EAAE,qBAAqB,CAAA;IAC5C,mBAAmB,EAAE,mBAAmB,CAAA;IACxC,KAAK,EAAE,KAAK,CAAA;IACZ,4FAA4F;IAC5F,YAAY,CAAC,EAAE,gBAAgB,CAAC,2BAA2B,EAAE,CAAC,CAAA;CAC/D;AAED;;;;;;;;;;;;;GAaG;AACH,qBAAa,iCAAiC;IAChC,OAAO,CAAC,QAAQ,CAAC,IAAI;IAAjC,YAA6B,IAAI,EAAE,sCAAsC,EAAI;IAE7E,sFAAsF;IAChF,QAAQ,CACZ,SAAS,EAAE,4BAA4B,EACvC,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,mBAAmB,EAAE,CAAC,CAOhC;IAED,oCAAoC;IAC9B,MAAM,CACV,SAAS,EAAE,4BAA4B,EACvC,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,8BAA8B,GACpC,OAAO,CAAC,mBAAmB,CAAC,CAsC9B;IAED,qFAAqF;IAC/E,MAAM,CACV,SAAS,EAAE,4BAA4B,EACvC,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,8BAA8B,GACpC,OAAO,CAAC,mBAAmB,CAAC,CAmC9B;IAED;;;;OAIG;IACG,MAAM,CACV,SAAS,EAAE,4BAA4B,EACvC,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,IAAI,CAAC,CAKf;IAED;;;;;OAKG;IACG,oBAAoB,CAAC,WAAW,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAkBhF;IAED,yEAAyE;IACnE,OAAO,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,2BAA2B,EAAE,CAAC,CAGzE;IAED;;;;;;;;OAQG;IACG,YAAY,CAChB,WAAW,EAAE,MAAM,EACnB,UAAU,EAAE,MAAM,EAAE,GACnB,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,mBAAmB,EAAE,CAAC,CAAC,CA0B7C;IAED,oFAAoF;IAC9E,UAAU,CAAC,SAAS,EAAE,4BAA4B,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAQxF;YAEa,WAAW;YAoBX,OAAO;IAUrB,OAAO,CAAC,sBAAsB;CAyC/B"}
@@ -0,0 +1,284 @@
1
+ import { ConflictError, NotFoundError, ValidationError, summarizeContract, } from '@cat-factory/kernel';
2
+ import { mergeFoundationalTiers, toWire } from './foundational-catalog.js';
3
+ import { validateUploadedContract } from './contract-validation.js';
4
+ /**
5
+ * The foundational-services catalog (docs/initiatives/foundational-services.md): per-tier CRUD
6
+ * plus the merged account ⊕ workspace resolve every design dispatch reads.
7
+ *
8
+ * Two properties are load-bearing and are why this is a service rather than a repository call:
9
+ *
10
+ * - **The catalog never carries a contract BODY.** `listTier` and `resolve` join the contract
11
+ * MANIFEST (id, format, title, byte size, operation names) onto each service in ONE extra
12
+ * query for the whole tier. The documents are read only by {@link contractsFor}, and only for
13
+ * the ids a caller asks for.
14
+ * - **A workspace row wins over an account row of the same id, and a workspace TOMBSTONE
15
+ * suppresses one.** That is what lets a board opt out of an org-wide service without an
16
+ * account admin, and it is why the merge reads the workspace tier `includeDeleted`.
17
+ */
18
+ export class FoundationalServiceCatalogService {
19
+ deps;
20
+ constructor(deps) {
21
+ this.deps = deps;
22
+ }
23
+ /** One tier's registered services, contract manifests joined on. Raw — not merged. */
24
+ async listTier(ownerKind, ownerId) {
25
+ const [records, manifest] = await Promise.all([
26
+ this.deps.foundationalServiceRepository.listByOwner(ownerKind, ownerId),
27
+ this.deps.apiContractRepository.listManifestByOwner(ownerKind, ownerId),
28
+ ]);
29
+ const byService = indexManifest(manifest);
30
+ return records.map((record) => toWire(record, byService.get(record.serviceId) ?? []));
31
+ }
32
+ /** Register a service at a tier. */
33
+ async create(ownerKind, ownerId, input) {
34
+ const existing = await this.deps.foundationalServiceRepository.get(ownerKind, ownerId, input.id);
35
+ // A LIVE row is a conflict; a tombstoned one is revived by this write. Refusing the
36
+ // revival would leave an id permanently unusable after one delete.
37
+ if (existing && !existing.deletedAt) {
38
+ throw new ConflictError(`Foundational service '${input.id}' already exists at this scope`, 'foundational_service_exists', { serviceId: input.id });
39
+ }
40
+ const now = this.deps.clock.now();
41
+ const record = {
42
+ serviceId: input.id,
43
+ ownerKind,
44
+ ownerId,
45
+ name: input.name,
46
+ summary: input.summary,
47
+ description: input.description,
48
+ capabilities: input.capabilities ?? [],
49
+ sourceId: null,
50
+ sourcePath: null,
51
+ pinnedCommit: null,
52
+ createdAt: existing?.createdAt ?? now,
53
+ updatedAt: now,
54
+ deletedAt: null,
55
+ };
56
+ await this.deps.foundationalServiceRepository.upsert(record);
57
+ const contracts = this.buildUploadedContracts(ownerKind, ownerId, input.id, input.contracts, now);
58
+ await this.deps.apiContractRepository.replaceForService(ownerKind, ownerId, input.id, contracts);
59
+ await this.invalidate(ownerKind, ownerId);
60
+ return toWire(record, contracts.map(manifestOf));
61
+ }
62
+ /** Patch a registered service. `contracts`, when present, replaces the whole set. */
63
+ async update(ownerKind, ownerId, serviceId, input) {
64
+ const existing = await this.require(ownerKind, ownerId, serviceId);
65
+ const now = this.deps.clock.now();
66
+ const record = {
67
+ ...existing,
68
+ name: input.name ?? existing.name,
69
+ summary: input.summary ?? existing.summary,
70
+ description: input.description ?? existing.description,
71
+ capabilities: input.capabilities ?? existing.capabilities,
72
+ updatedAt: now,
73
+ };
74
+ await this.deps.foundationalServiceRepository.upsert(record);
75
+ let manifest;
76
+ if (input.contracts) {
77
+ const contracts = this.buildUploadedContracts(ownerKind, ownerId, serviceId, input.contracts, now);
78
+ await this.deps.apiContractRepository.replaceForService(ownerKind, ownerId, serviceId, contracts);
79
+ manifest = contracts.map(manifestOf);
80
+ }
81
+ else {
82
+ manifest = (await this.deps.apiContractRepository.listManifestByOwner(ownerKind, ownerId)).filter((entry) => entry.serviceId === serviceId);
83
+ }
84
+ await this.invalidate(ownerKind, ownerId);
85
+ return toWire(record, manifest);
86
+ }
87
+ /**
88
+ * Remove a service from a tier. A WORKSPACE removal writes a tombstone (which is also how a
89
+ * board suppresses an inherited account service); an ACCOUNT removal drops the row and its
90
+ * documents outright, since there is no higher tier for it to shadow.
91
+ */
92
+ async remove(ownerKind, ownerId, serviceId) {
93
+ const now = this.deps.clock.now();
94
+ await this.deps.foundationalServiceRepository.softDelete(ownerKind, ownerId, serviceId, now);
95
+ await this.deps.apiContractRepository.deleteForService(ownerKind, ownerId, serviceId);
96
+ await this.invalidate(ownerKind, ownerId);
97
+ }
98
+ /**
99
+ * Suppress an ACCOUNT service for one workspace by writing a workspace-tier tombstone. The
100
+ * row carries no content of its own — it exists only to lose the merge — so it is written
101
+ * here rather than through {@link create}, which would demand a name and summary for
102
+ * something that is never rendered.
103
+ */
104
+ async suppressForWorkspace(workspaceId, serviceId) {
105
+ const now = this.deps.clock.now();
106
+ await this.deps.foundationalServiceRepository.upsert({
107
+ serviceId,
108
+ ownerKind: 'workspace',
109
+ ownerId: workspaceId,
110
+ name: '',
111
+ summary: '',
112
+ description: '',
113
+ capabilities: [],
114
+ sourceId: null,
115
+ sourcePath: null,
116
+ pinnedCommit: null,
117
+ createdAt: now,
118
+ updatedAt: now,
119
+ deletedAt: now,
120
+ });
121
+ await this.invalidate('workspace', workspaceId);
122
+ }
123
+ /** The merged catalog a workspace's agents see, cached per workspace. */
124
+ async resolve(workspaceId) {
125
+ if (!this.deps.catalogCache)
126
+ return this.loadCatalog(workspaceId);
127
+ return this.deps.catalogCache.get(workspaceId, workspaceId, () => this.loadCatalog(workspaceId));
128
+ }
129
+ /**
130
+ * The LAZY contract read: full documents for the named services, resolved against the same
131
+ * account ⊕ workspace precedence the catalog uses. One batched query per tier — never a
132
+ * per-service read — and an empty `serviceIds` returns `[]` without touching the store.
133
+ *
134
+ * Precedence matters here as much as in the catalog: a workspace that overrode a service
135
+ * must hand out ITS documents, or the design would be reviewed against the org's spec while
136
+ * the code is written against the board's.
137
+ */
138
+ async contractsFor(workspaceId, serviceIds) {
139
+ const wanted = [...new Set(serviceIds)].filter(Boolean);
140
+ if (wanted.length === 0)
141
+ return new Map();
142
+ const accountId = await this.deps.workspaceRepository.accountOf(workspaceId);
143
+ const [workspaceDocs, accountDocs] = await Promise.all([
144
+ this.deps.apiContractRepository.listByServiceIds('workspace', workspaceId, wanted),
145
+ accountId
146
+ ? this.deps.apiContractRepository.listByServiceIds('account', accountId, wanted)
147
+ : Promise.resolve([]),
148
+ ]);
149
+ // Which TIER won for each id is decided by the catalog merge, not by which tier happens to
150
+ // hold documents: a workspace row that overrides an account one but ships no contract of
151
+ // its own must NOT silently inherit the account's documents, or the override reads as
152
+ // partially applied. So resolve the winning tier first, then take that tier's documents.
153
+ const catalog = await this.resolve(workspaceId);
154
+ const tierById = new Map(catalog.map((entry) => [entry.id, entry.tier]));
155
+ const out = new Map();
156
+ for (const id of wanted) {
157
+ const tier = tierById.get(id);
158
+ if (!tier)
159
+ continue;
160
+ const docs = (tier === 'workspace' ? workspaceDocs : accountDocs).filter((doc) => doc.serviceId === id);
161
+ out.set(id, docs.map(documentOf));
162
+ }
163
+ return out;
164
+ }
165
+ /** Drop a tier's cached catalog. An account write invalidates every workspace's. */
166
+ async invalidate(ownerKind, ownerId) {
167
+ if (!this.deps.catalogCache)
168
+ return;
169
+ // A workspace write affects exactly one group; an account write affects every workspace in
170
+ // the account, and enumerating them would be a read per invalidation — over-invalidation is
171
+ // always safe, so the account path drops everything (the same call `FragmentLibraryService`
172
+ // makes for the same reason).
173
+ if (ownerKind === 'workspace')
174
+ await this.deps.catalogCache.invalidateGroup(ownerId);
175
+ else
176
+ await this.deps.catalogCache.invalidateAll();
177
+ }
178
+ async loadCatalog(workspaceId) {
179
+ const accountId = await this.deps.workspaceRepository.accountOf(workspaceId);
180
+ const [workspaceRows, accountRows, workspaceManifest, accountManifest] = await Promise.all([
181
+ this.deps.foundationalServiceRepository.listByOwner('workspace', workspaceId, true),
182
+ accountId
183
+ ? this.deps.foundationalServiceRepository.listByOwner('account', accountId)
184
+ : Promise.resolve([]),
185
+ this.deps.apiContractRepository.listManifestByOwner('workspace', workspaceId),
186
+ accountId
187
+ ? this.deps.apiContractRepository.listManifestByOwner('account', accountId)
188
+ : Promise.resolve([]),
189
+ ]);
190
+ return mergeFoundationalTiers({
191
+ accountRows,
192
+ workspaceRows,
193
+ accountManifest: indexManifest(accountManifest),
194
+ workspaceManifest: indexManifest(workspaceManifest),
195
+ });
196
+ }
197
+ async require(ownerKind, ownerId, serviceId) {
198
+ const record = await this.deps.foundationalServiceRepository.get(ownerKind, ownerId, serviceId);
199
+ if (!record || record.deletedAt)
200
+ throw new NotFoundError('FoundationalService', serviceId);
201
+ return record;
202
+ }
203
+ buildUploadedContracts(ownerKind, ownerId, serviceId, uploads, now) {
204
+ const seen = new Set();
205
+ return (uploads ?? []).map((upload) => {
206
+ if (seen.has(upload.contractId)) {
207
+ throw new ValidationError(`Duplicate contract id '${upload.contractId}'`, {
208
+ reason: 'duplicate_contract_id',
209
+ contractId: upload.contractId,
210
+ });
211
+ }
212
+ seen.add(upload.contractId);
213
+ validateUploadedContract(upload);
214
+ const summary = summarizeContract({
215
+ contractId: upload.contractId,
216
+ format: upload.format,
217
+ title: upload.title,
218
+ path: null,
219
+ body: upload.body,
220
+ });
221
+ return {
222
+ ownerKind,
223
+ ownerId,
224
+ serviceId,
225
+ contractId: upload.contractId,
226
+ format: upload.format,
227
+ title: upload.title,
228
+ body: upload.body,
229
+ operations: summary.operations,
230
+ omittedOperations: summary.omittedOperations,
231
+ sourcePath: null,
232
+ sourceSha: null,
233
+ createdAt: now,
234
+ updatedAt: now,
235
+ };
236
+ });
237
+ }
238
+ }
239
+ function indexManifest(entries) {
240
+ const map = new Map();
241
+ for (const entry of entries) {
242
+ const list = map.get(entry.serviceId);
243
+ if (list)
244
+ list.push(entry);
245
+ else
246
+ map.set(entry.serviceId, [entry]);
247
+ }
248
+ return map;
249
+ }
250
+ /**
251
+ * A just-written contract record → its manifest entry. The operation index is recomputed from
252
+ * the body here rather than read back, so a create/update response describes exactly what was
253
+ * stored without a second round trip.
254
+ */
255
+ function manifestOf(record) {
256
+ return {
257
+ serviceId: record.serviceId,
258
+ contractId: record.contractId,
259
+ format: record.format,
260
+ title: record.title,
261
+ size: record.body.length,
262
+ operations: record.operations,
263
+ omittedOperations: record.omittedOperations,
264
+ sourcePath: record.sourcePath,
265
+ };
266
+ }
267
+ /**
268
+ * A stored record → the wire document. The operation index is read off the ROW rather than
269
+ * recomputed: it is the same index the catalog showed, so a consumer can never see a document
270
+ * whose operation list disagrees with the one the design was chosen from.
271
+ */
272
+ function documentOf(record) {
273
+ return {
274
+ contractId: record.contractId,
275
+ format: record.format,
276
+ title: record.title,
277
+ size: record.body.length,
278
+ path: record.sourcePath,
279
+ operations: record.operations,
280
+ omittedOperations: record.omittedOperations,
281
+ body: record.body,
282
+ };
283
+ }
284
+ //# sourceMappingURL=FoundationalServiceCatalogService.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FoundationalServiceCatalogService.js","sourceRoot":"","sources":["../../src/foundationalServices/FoundationalServiceCatalogService.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,37 @@
1
+ import type { FoundationalServiceSelection } from '@cat-factory/contracts';
2
+ import type { FoundationalCatalogView, InjectedContextFile } from '@cat-factory/kernel';
3
+ import type { FoundationalServiceCatalogService } from './FoundationalServiceCatalogService.js';
4
+ /**
5
+ * The engine-facing seam over the foundational-services catalog
6
+ * (docs/initiatives/foundational-services.md). Two reads, deliberately separate, because they
7
+ * serve the two halves of the feature:
8
+ *
9
+ * - {@link catalogFor} is the DESIGN-time read: identity, capabilities and operation names for
10
+ * every registered service, no document bodies. Folded into the Architect's prompt.
11
+ * - {@link contextFilesFor} is the CONSUMER-time read: the full contract documents, for exactly
12
+ * the ids the design declared, materialised as `.cat-context/foundational-services/*` files.
13
+ *
14
+ * Implemented against the catalog SERVICE rather than the repositories so both reads go through
15
+ * the same tier merge and the same cache — a consumer can never be handed the account's
16
+ * document for a service the design chose at the workspace tier.
17
+ */
18
+ export declare class FoundationalServiceRunResolver {
19
+ private readonly catalog;
20
+ constructor(catalog: FoundationalServiceCatalogService);
21
+ /** The catalog the design step is shown. Empty array when nothing is registered. */
22
+ catalogFor(workspaceId: string): Promise<FoundationalCatalogView[]>;
23
+ /** Just the ids in the merged catalog — the check a settled design's declaration runs against. */
24
+ catalogIdsFor(workspaceId: string): Promise<string[]>;
25
+ /**
26
+ * The injected context files a consumer kind receives for a design's declared services: one
27
+ * markdown file per service carrying its contract documents, plus an index.
28
+ *
29
+ * The INDEX is always produced, even when nothing was declared and nothing resolved. That is
30
+ * the "degrade loudly" half: a coder handed no foundational context cannot otherwise tell
31
+ * "the design decided none apply" from "the design step never ran" from "the design named a
32
+ * service this deployment does not have", and those need three different reactions.
33
+ */
34
+ contextFilesFor(workspaceId: string, selection: FoundationalServiceSelection | undefined): Promise<InjectedContextFile[]>;
35
+ private hasCatalog;
36
+ }
37
+ //# sourceMappingURL=FoundationalServiceRunResolver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FoundationalServiceRunResolver.d.ts","sourceRoot":"","sources":["../../src/foundationalServices/FoundationalServiceRunResolver.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,wBAAwB,CAAA;AAC1E,OAAO,KAAK,EAAE,uBAAuB,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAA;AAOvF,OAAO,KAAK,EAAE,iCAAiC,EAAE,MAAM,wCAAwC,CAAA;AAE/F;;;;;;;;;;;;;GAaG;AACH,qBAAa,8BAA8B;IAC7B,OAAO,CAAC,QAAQ,CAAC,OAAO;IAApC,YAA6B,OAAO,EAAE,iCAAiC,EAAI;IAE3E,oFAAoF;IAC9E,UAAU,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,uBAAuB,EAAE,CAAC,CAUxE;IAED,kGAAkG;IAC5F,aAAa,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAE1D;IAED;;;;;;;;OAQG;IACG,eAAe,CACnB,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,4BAA4B,GAAG,SAAS,GAClD,OAAO,CAAC,mBAAmB,EAAE,CAAC,CAyChC;YAEa,UAAU;CAGzB"}
@@ -0,0 +1,92 @@
1
+ import { FOUNDATIONAL_INDEX_FILE, contextFileFor, renderContractDocument, renderFoundationalIndex, } from '@cat-factory/kernel';
2
+ /**
3
+ * The engine-facing seam over the foundational-services catalog
4
+ * (docs/initiatives/foundational-services.md). Two reads, deliberately separate, because they
5
+ * serve the two halves of the feature:
6
+ *
7
+ * - {@link catalogFor} is the DESIGN-time read: identity, capabilities and operation names for
8
+ * every registered service, no document bodies. Folded into the Architect's prompt.
9
+ * - {@link contextFilesFor} is the CONSUMER-time read: the full contract documents, for exactly
10
+ * the ids the design declared, materialised as `.cat-context/foundational-services/*` files.
11
+ *
12
+ * Implemented against the catalog SERVICE rather than the repositories so both reads go through
13
+ * the same tier merge and the same cache — a consumer can never be handed the account's
14
+ * document for a service the design chose at the workspace tier.
15
+ */
16
+ export class FoundationalServiceRunResolver {
17
+ catalog;
18
+ constructor(catalog) {
19
+ this.catalog = catalog;
20
+ }
21
+ /** The catalog the design step is shown. Empty array when nothing is registered. */
22
+ async catalogFor(workspaceId) {
23
+ const resolved = await this.catalog.resolve(workspaceId);
24
+ return resolved.map((service) => ({
25
+ id: service.id,
26
+ name: service.name,
27
+ summary: service.summary,
28
+ description: service.description,
29
+ capabilities: service.capabilities,
30
+ contracts: service.contracts,
31
+ }));
32
+ }
33
+ /** Just the ids in the merged catalog — the check a settled design's declaration runs against. */
34
+ async catalogIdsFor(workspaceId) {
35
+ return (await this.catalog.resolve(workspaceId)).map((service) => service.id);
36
+ }
37
+ /**
38
+ * The injected context files a consumer kind receives for a design's declared services: one
39
+ * markdown file per service carrying its contract documents, plus an index.
40
+ *
41
+ * The INDEX is always produced, even when nothing was declared and nothing resolved. That is
42
+ * the "degrade loudly" half: a coder handed no foundational context cannot otherwise tell
43
+ * "the design decided none apply" from "the design step never ran" from "the design named a
44
+ * service this deployment does not have", and those need three different reactions.
45
+ */
46
+ async contextFilesFor(workspaceId, selection) {
47
+ const declared = selection?.declared ?? [];
48
+ const unknown = selection?.unknown ?? [];
49
+ const noDeclaration = selection === undefined;
50
+ // Nothing to say and nothing to warn about: a run in a deployment that registers no
51
+ // foundational services at all would otherwise pay for an index file on every dispatch
52
+ // saying so. A declaration that resolved to nothing is DIFFERENT and does get the file.
53
+ if (noDeclaration && unknown.length === 0 && !(await this.hasCatalog(workspaceId)))
54
+ return [];
55
+ const documents = await this.catalog.contractsFor(workspaceId, declared);
56
+ const resolved = await this.catalog.resolve(workspaceId);
57
+ const byId = new Map(resolved.map((service) => [service.id, service]));
58
+ const bundles = declared
59
+ .map((id) => {
60
+ const service = byId.get(id);
61
+ if (!service)
62
+ return null;
63
+ return {
64
+ id: service.id,
65
+ name: service.name,
66
+ summary: service.summary,
67
+ description: service.description,
68
+ contracts: (documents.get(id) ?? []).map((doc) => ({
69
+ contractId: doc.contractId,
70
+ format: doc.format,
71
+ title: doc.title,
72
+ body: doc.body,
73
+ })),
74
+ };
75
+ })
76
+ .filter((bundle) => bundle !== null);
77
+ const files = [
78
+ {
79
+ path: FOUNDATIONAL_INDEX_FILE,
80
+ content: renderFoundationalIndex({ bundles, unknown, noDeclaration }),
81
+ },
82
+ ];
83
+ for (const bundle of bundles) {
84
+ files.push({ path: contextFileFor(bundle.id), content: renderContractDocument(bundle) });
85
+ }
86
+ return files;
87
+ }
88
+ async hasCatalog(workspaceId) {
89
+ return (await this.catalog.resolve(workspaceId)).length > 0;
90
+ }
91
+ }
92
+ //# sourceMappingURL=FoundationalServiceRunResolver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FoundationalServiceRunResolver.js","sourceRoot":"","sources":["../../src/foundationalServices/FoundationalServiceRunResolver.ts"],"names":[],"mappings":"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"}