@cat-factory/integrations 0.118.1 → 0.120.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.
- package/dist/modules/capabilityCredentials/CapabilityCredentialsService.d.ts +36 -8
- package/dist/modules/capabilityCredentials/CapabilityCredentialsService.d.ts.map +1 -1
- package/dist/modules/capabilityCredentials/CapabilityCredentialsService.js +119 -34
- package/dist/modules/capabilityCredentials/CapabilityCredentialsService.js.map +1 -1
- package/dist/modules/environments/EnvironmentTeardownService.d.ts +38 -3
- package/dist/modules/environments/EnvironmentTeardownService.d.ts.map +1 -1
- package/dist/modules/environments/EnvironmentTeardownService.js +32 -4
- package/dist/modules/environments/EnvironmentTeardownService.js.map +1 -1
- package/package.json +4 -4
|
@@ -30,6 +30,14 @@ export interface CapabilityCredentialsServiceDependencies {
|
|
|
30
30
|
* `BinaryGeneratorSource` rather than a registry anyway, which only the composition root can
|
|
31
31
|
* supply.
|
|
32
32
|
*
|
|
33
|
+
* CONCURRENCY: the row is ONE sealed blob holding the whole set, so the per-key writes
|
|
34
|
+
* ({@link put}, {@link remove}) are read-modify-write over it and ride a rev-guarded
|
|
35
|
+
* `compareAndSwap`: a blind upsert would let two operators saving DIFFERENT keys silently
|
|
36
|
+
* destroy each other's, with the loser's save still returning success. On a lost race the
|
|
37
|
+
* mutation reloads and re-applies on the winner's snapshot (it is a pure single-key edit, so
|
|
38
|
+
* re-application is trivially idempotent). The whole-set {@link set} stays a blind write on
|
|
39
|
+
* purpose: replacing whatever is stored IS its semantics.
|
|
40
|
+
*
|
|
33
41
|
* {@link resolveValues} is the one place a value is decrypted, and its result reaches nothing but
|
|
34
42
|
* the job body.
|
|
35
43
|
*/
|
|
@@ -47,17 +55,24 @@ export declare class CapabilityCredentialsService {
|
|
|
47
55
|
*
|
|
48
56
|
* A whole-set REPLACE rather than a merge, because the client cannot merge: it never received
|
|
49
57
|
* the values, so a partial write would have to mean "leave the ones I did not send alone",
|
|
50
|
-
* which is indistinguishable on the wire from "remove them". {@link
|
|
51
|
-
*
|
|
58
|
+
* which is indistinguishable on the wire from "remove them". {@link put} and {@link remove}
|
|
59
|
+
* are the narrow operations that would otherwise need that distinction.
|
|
52
60
|
*/
|
|
53
61
|
set(workspaceId: string, input: UpsertCapabilityCredentialsInput): Promise<CapabilityCredentialRef[]>;
|
|
54
62
|
/**
|
|
55
|
-
*
|
|
63
|
+
* Set ONE credential's value, leaving the rest sealed as they are. Replaces the value when the
|
|
64
|
+
* key is already stored, appends it otherwise.
|
|
56
65
|
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
66
|
+
* The narrow twin of {@link remove}, and the write a checklist UI actually performs: the
|
|
67
|
+
* client holds no values, so it can neither re-send the set nor express "leave the others
|
|
68
|
+
* alone" through {@link set}. Rev-guarded (see the class doc), so two operators saving
|
|
69
|
+
* different keys at the same moment both land.
|
|
70
|
+
*/
|
|
71
|
+
put(workspaceId: string, key: string, value: string): Promise<CapabilityCredentialRef[]>;
|
|
72
|
+
/**
|
|
73
|
+
* Remove ONE stored credential, leaving the rest sealed as they are. Re-seals the survivors
|
|
74
|
+
* rather than editing the blob in place, rev-guarded like {@link put}; emptying the set
|
|
75
|
+
* deletes the row through the same guard, so a concurrent save's key cannot be swept away.
|
|
61
76
|
*/
|
|
62
77
|
remove(workspaceId: string, key: string): Promise<CapabilityCredentialRef[]>;
|
|
63
78
|
/**
|
|
@@ -66,7 +81,20 @@ export declare class CapabilityCredentialsService {
|
|
|
66
81
|
* fallback (where a facade wired one) should answer instead.
|
|
67
82
|
*/
|
|
68
83
|
resolveValues(workspaceId: string): Promise<CapabilityCredentialEntry[]>;
|
|
69
|
-
|
|
84
|
+
/**
|
|
85
|
+
* Apply a pure single-key mutation under OPTIMISTIC CONCURRENCY: load, run `apply`, then
|
|
86
|
+
* `compareAndSwap` (or the rev-guarded delete when the set empties); on a lost race reload and
|
|
87
|
+
* re-apply on the winner's snapshot, bounded by {@link MAX_MUTATE_ATTEMPTS}.
|
|
88
|
+
*
|
|
89
|
+
* `apply` returns the next entry set, or null for "nothing to change" (a duplicate delete),
|
|
90
|
+
* which answers with the current summary untouched. `touchedKey` is the one key whose
|
|
91
|
+
* `updatedAt` this write may honestly stamp: "last set" is a per-key fact, so every OTHER
|
|
92
|
+
* entry keeps the timestamp the stored summary already carries. A write that re-stamped the
|
|
93
|
+
* whole set would falsify every neighbour's date.
|
|
94
|
+
*/
|
|
95
|
+
private mutate;
|
|
96
|
+
/** Decrypt and validate the sealed entry blob; an absent row is an empty set. */
|
|
97
|
+
private decryptEntries;
|
|
70
98
|
/** Parse the persisted non-secret summary, tolerating a corrupt row (the view still loads). */
|
|
71
99
|
private parseSummary;
|
|
72
100
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CapabilityCredentialsService.d.ts","sourceRoot":"","sources":["../../../src/modules/capabilityCredentials/CapabilityCredentialsService.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,8BAA8B,EAC9B,KAAK,EACL,YAAY,EACb,MAAM,qBAAqB,CAAA;
|
|
1
|
+
{"version":3,"file":"CapabilityCredentialsService.d.ts","sourceRoot":"","sources":["../../../src/modules/capabilityCredentials/CapabilityCredentialsService.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,8BAA8B,EAC9B,KAAK,EACL,YAAY,EACb,MAAM,qBAAqB,CAAA;AAE5B,OAAO,KAAK,EACV,yBAAyB,EACzB,uBAAuB,EACvB,gCAAgC,EACjC,MAAM,wBAAwB,CAAA;AAO/B;;;;GAIG;AACH,eAAO,MAAM,kCAAkC,uCAAuC,CAAA;AAUtF,MAAM,WAAW,wCAAwC;IACvD,8BAA8B,EAAE,8BAA8B,CAAA;IAC9D,4FAA4F;IAC5F,YAAY,EAAE,YAAY,CAAA;IAC1B,KAAK,EAAE,KAAK,CAAA;CACb;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,qBAAa,4BAA4B;IAC3B,OAAO,CAAC,QAAQ,CAAC,IAAI;IAAjC,YAA6B,IAAI,EAAE,wCAAwC,EAAI;IAE/E;;;OAGG;IACG,UAAU,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,uBAAuB,EAAE,CAAC,CAGxE;IAED;;;;;;;;OAQG;IACG,GAAG,CACP,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,gCAAgC,GACtC,OAAO,CAAC,uBAAuB,EAAE,CAAC,CAoBpC;IAED;;;;;;;;OAQG;IACG,GAAG,CAAC,WAAW,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,uBAAuB,EAAE,CAAC,CAe7F;IAED;;;;OAIG;IACG,MAAM,CAAC,WAAW,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,uBAAuB,EAAE,CAAC,CAQjF;IAED;;;;OAIG;IACG,aAAa,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,yBAAyB,EAAE,CAAC,CAE7E;IAED;;;;;;;;;;OAUG;YACW,MAAM;IA0CpB,iFAAiF;YACnE,cAAc;IAS5B,+FAA+F;IAC/F,OAAO,CAAC,YAAY;CAerB"}
|
|
@@ -1,10 +1,18 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ConflictError, ValidationError } from '@cat-factory/kernel';
|
|
2
|
+
import { MAX_CAPABILITY_CREDENTIALS, capabilityCredentialsSummary, parseCapabilityCredentialEntries, } from '@cat-factory/contracts';
|
|
2
3
|
/**
|
|
3
4
|
* HKDF domain tag separating the sealed capability-credential blob from every other cipher
|
|
4
5
|
* (mirrors {@link TEST_SECRETS_CIPHER_INFO} et al). The facade builds a `WebCryptoSecretCipher`
|
|
5
6
|
* keyed by `ENCRYPTION_KEY` with this info tag.
|
|
6
7
|
*/
|
|
7
8
|
export const CAPABILITY_CREDENTIALS_CIPHER_INFO = 'cat-factory:capability-credentials';
|
|
9
|
+
/**
|
|
10
|
+
* How many times a contended per-key mutation reloads and re-applies before giving up with a
|
|
11
|
+
* 409. Matches `IterativeReviewService`'s budget: the contending writers here are human saves in
|
|
12
|
+
* two browser tabs, so a handful of retries covers real contention while still failing loudly on
|
|
13
|
+
* a pathological hot row.
|
|
14
|
+
*/
|
|
15
|
+
const MAX_MUTATE_ATTEMPTS = 8;
|
|
8
16
|
/**
|
|
9
17
|
* Owns a workspace's capability credentials: the tenant-scoped, sealed home for the secrets a
|
|
10
18
|
* tool server or a generative binary integration declares by name.
|
|
@@ -23,6 +31,14 @@ export const CAPABILITY_CREDENTIALS_CIPHER_INFO = 'cat-factory:capability-creden
|
|
|
23
31
|
* `BinaryGeneratorSource` rather than a registry anyway, which only the composition root can
|
|
24
32
|
* supply.
|
|
25
33
|
*
|
|
34
|
+
* CONCURRENCY: the row is ONE sealed blob holding the whole set, so the per-key writes
|
|
35
|
+
* ({@link put}, {@link remove}) are read-modify-write over it and ride a rev-guarded
|
|
36
|
+
* `compareAndSwap`: a blind upsert would let two operators saving DIFFERENT keys silently
|
|
37
|
+
* destroy each other's, with the loser's save still returning success. On a lost race the
|
|
38
|
+
* mutation reloads and re-applies on the winner's snapshot (it is a pure single-key edit, so
|
|
39
|
+
* re-application is trivially idempotent). The whole-set {@link set} stays a blind write on
|
|
40
|
+
* purpose: replacing whatever is stored IS its semantics.
|
|
41
|
+
*
|
|
26
42
|
* {@link resolveValues} is the one place a value is decrypted, and its result reaches nothing but
|
|
27
43
|
* the job body.
|
|
28
44
|
*/
|
|
@@ -45,36 +61,68 @@ export class CapabilityCredentialsService {
|
|
|
45
61
|
*
|
|
46
62
|
* A whole-set REPLACE rather than a merge, because the client cannot merge: it never received
|
|
47
63
|
* the values, so a partial write would have to mean "leave the ones I did not send alone",
|
|
48
|
-
* which is indistinguishable on the wire from "remove them". {@link
|
|
49
|
-
*
|
|
64
|
+
* which is indistinguishable on the wire from "remove them". {@link put} and {@link remove}
|
|
65
|
+
* are the narrow operations that would otherwise need that distinction.
|
|
50
66
|
*/
|
|
51
67
|
async set(workspaceId, input) {
|
|
52
68
|
if (input.entries.length === 0) {
|
|
53
69
|
await this.deps.capabilityCredentialRepository.delete(workspaceId);
|
|
54
70
|
return [];
|
|
55
71
|
}
|
|
56
|
-
|
|
72
|
+
const now = this.deps.clock.now();
|
|
73
|
+
const existing = await this.deps.capabilityCredentialRepository.get(workspaceId);
|
|
74
|
+
// Every value in the set was just (re)typed, so every key is honestly stamped `now`.
|
|
75
|
+
const summary = capabilityCredentialsSummary(input.entries, now);
|
|
76
|
+
await this.deps.capabilityCredentialRepository.upsert({
|
|
77
|
+
workspaceId,
|
|
78
|
+
credentials: await this.deps.secretCipher.encrypt(JSON.stringify(input.entries)),
|
|
79
|
+
summary: JSON.stringify(summary),
|
|
80
|
+
// The insert value; a conflicting update bumps the STORED rev in SQL instead, so the rev
|
|
81
|
+
// stays monotonic under this blind write and a concurrent compareAndSwap reliably loses.
|
|
82
|
+
rev: 0,
|
|
83
|
+
createdAt: existing?.createdAt ?? now,
|
|
84
|
+
updatedAt: now,
|
|
85
|
+
});
|
|
86
|
+
return summary;
|
|
57
87
|
}
|
|
58
88
|
/**
|
|
59
|
-
*
|
|
89
|
+
* Set ONE credential's value, leaving the rest sealed as they are. Replaces the value when the
|
|
90
|
+
* key is already stored, appends it otherwise.
|
|
60
91
|
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
92
|
+
* The narrow twin of {@link remove}, and the write a checklist UI actually performs: the
|
|
93
|
+
* client holds no values, so it can neither re-send the set nor express "leave the others
|
|
94
|
+
* alone" through {@link set}. Rev-guarded (see the class doc), so two operators saving
|
|
95
|
+
* different keys at the same moment both land.
|
|
96
|
+
*/
|
|
97
|
+
async put(workspaceId, key, value) {
|
|
98
|
+
return this.mutate(workspaceId, key, (entries) => {
|
|
99
|
+
const existing = entries.findIndex((entry) => entry.key === key);
|
|
100
|
+
if (existing === -1 && entries.length >= MAX_CAPABILITY_CREDENTIALS) {
|
|
101
|
+
throw new ValidationError(`at most ${MAX_CAPABILITY_CREDENTIALS} capability credentials per workspace`, { reason: 'capability_credential_limit' });
|
|
102
|
+
}
|
|
103
|
+
const next = [...entries];
|
|
104
|
+
// Replace IN PLACE for a known key, so re-typing a value does not reorder the stored set.
|
|
105
|
+
if (existing === -1)
|
|
106
|
+
next.push({ key, value });
|
|
107
|
+
else
|
|
108
|
+
next[existing] = { key, value };
|
|
109
|
+
return next;
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Remove ONE stored credential, leaving the rest sealed as they are. Re-seals the survivors
|
|
114
|
+
* rather than editing the blob in place, rev-guarded like {@link put}; emptying the set
|
|
115
|
+
* deletes the row through the same guard, so a concurrent save's key cannot be swept away.
|
|
65
116
|
*/
|
|
66
117
|
async remove(workspaceId, key) {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
return [];
|
|
76
|
-
}
|
|
77
|
-
return this.write(workspaceId, remaining);
|
|
118
|
+
return this.mutate(workspaceId, null, (entries) => {
|
|
119
|
+
const remaining = entries.filter((entry) => entry.key !== key);
|
|
120
|
+
// Nothing matched: answer with the current state rather than rewriting the blob, so a
|
|
121
|
+
// duplicate delete is a no-op instead of a pointless re-seal.
|
|
122
|
+
if (remaining.length === entries.length)
|
|
123
|
+
return null;
|
|
124
|
+
return remaining;
|
|
125
|
+
});
|
|
78
126
|
}
|
|
79
127
|
/**
|
|
80
128
|
* The DECRYPTED credential set for a workspace — read once per dispatch by the composed
|
|
@@ -82,24 +130,61 @@ export class CapabilityCredentialsService {
|
|
|
82
130
|
* fallback (where a facade wired one) should answer instead.
|
|
83
131
|
*/
|
|
84
132
|
async resolveValues(workspaceId) {
|
|
85
|
-
|
|
133
|
+
return this.decryptEntries(await this.deps.capabilityCredentialRepository.get(workspaceId));
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Apply a pure single-key mutation under OPTIMISTIC CONCURRENCY: load, run `apply`, then
|
|
137
|
+
* `compareAndSwap` (or the rev-guarded delete when the set empties); on a lost race reload and
|
|
138
|
+
* re-apply on the winner's snapshot, bounded by {@link MAX_MUTATE_ATTEMPTS}.
|
|
139
|
+
*
|
|
140
|
+
* `apply` returns the next entry set, or null for "nothing to change" (a duplicate delete),
|
|
141
|
+
* which answers with the current summary untouched. `touchedKey` is the one key whose
|
|
142
|
+
* `updatedAt` this write may honestly stamp: "last set" is a per-key fact, so every OTHER
|
|
143
|
+
* entry keeps the timestamp the stored summary already carries. A write that re-stamped the
|
|
144
|
+
* whole set would falsify every neighbour's date.
|
|
145
|
+
*/
|
|
146
|
+
async mutate(workspaceId, touchedKey, apply) {
|
|
147
|
+
const repo = this.deps.capabilityCredentialRepository;
|
|
148
|
+
for (let attempt = 0; attempt < MAX_MUTATE_ATTEMPTS; attempt++) {
|
|
149
|
+
const record = await repo.get(workspaceId);
|
|
150
|
+
const next = apply(await this.decryptEntries(record));
|
|
151
|
+
if (next === null)
|
|
152
|
+
return this.parseSummary(record);
|
|
153
|
+
if (next.length === 0) {
|
|
154
|
+
if (!record)
|
|
155
|
+
return [];
|
|
156
|
+
if (await repo.deleteIfRev(workspaceId, record.rev))
|
|
157
|
+
return [];
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
const now = this.deps.clock.now();
|
|
161
|
+
const prior = new Map(this.parseSummary(record).map((ref) => [ref.key, ref.updatedAt]));
|
|
162
|
+
const summary = next.map((entry) => ({
|
|
163
|
+
key: entry.key,
|
|
164
|
+
updatedAt: entry.key === touchedKey ? now : (prior.get(entry.key) ?? now),
|
|
165
|
+
}));
|
|
166
|
+
const swapped = await repo.compareAndSwap({
|
|
167
|
+
workspaceId,
|
|
168
|
+
credentials: await this.deps.secretCipher.encrypt(JSON.stringify(next)),
|
|
169
|
+
summary: JSON.stringify(summary),
|
|
170
|
+
rev: (record?.rev ?? -1) + 1,
|
|
171
|
+
createdAt: record?.createdAt ?? now,
|
|
172
|
+
updatedAt: now,
|
|
173
|
+
}, record?.rev ?? null);
|
|
174
|
+
if (swapped)
|
|
175
|
+
return summary;
|
|
176
|
+
// Lost the race: loop back, reload, re-apply on the winner's snapshot.
|
|
177
|
+
}
|
|
178
|
+
// No `reason` code, same call as ReviewContendedError: the SPA has no conflict-specific
|
|
179
|
+
// handling beyond "retry", which the message already says.
|
|
180
|
+
throw new ConflictError(`capability credentials for workspace '${workspaceId}' are being modified concurrently; retry`);
|
|
181
|
+
}
|
|
182
|
+
/** Decrypt and validate the sealed entry blob; an absent row is an empty set. */
|
|
183
|
+
async decryptEntries(record) {
|
|
86
184
|
if (!record)
|
|
87
185
|
return [];
|
|
88
186
|
return parseCapabilityCredentialEntries(JSON.parse(await this.deps.secretCipher.decrypt(record.credentials)));
|
|
89
187
|
}
|
|
90
|
-
async write(workspaceId, entries) {
|
|
91
|
-
const now = this.deps.clock.now();
|
|
92
|
-
const existing = await this.deps.capabilityCredentialRepository.get(workspaceId);
|
|
93
|
-
const summary = capabilityCredentialsSummary(entries, now);
|
|
94
|
-
await this.deps.capabilityCredentialRepository.upsert({
|
|
95
|
-
workspaceId,
|
|
96
|
-
credentials: await this.deps.secretCipher.encrypt(JSON.stringify(entries)),
|
|
97
|
-
summary: JSON.stringify(summary),
|
|
98
|
-
createdAt: existing?.createdAt ?? now,
|
|
99
|
-
updatedAt: now,
|
|
100
|
-
});
|
|
101
|
-
return summary;
|
|
102
|
-
}
|
|
103
188
|
/** Parse the persisted non-secret summary, tolerating a corrupt row (the view still loads). */
|
|
104
189
|
parseSummary(record) {
|
|
105
190
|
if (!record)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CapabilityCredentialsService.js","sourceRoot":"","sources":["../../../src/modules/capabilityCredentials/CapabilityCredentialsService.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"CapabilityCredentialsService.js","sourceRoot":"","sources":["../../../src/modules/capabilityCredentials/CapabilityCredentialsService.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,9 +1,26 @@
|
|
|
1
|
-
import type { Clock } from '@cat-factory/kernel';
|
|
2
|
-
import type { EnvironmentRegistryRepository } from '@cat-factory/kernel';
|
|
1
|
+
import type { Clock, Logger, ProvisioningOutcome } from '@cat-factory/kernel';
|
|
2
|
+
import type { EnvironmentRecord, EnvironmentRegistryRepository } from '@cat-factory/kernel';
|
|
3
3
|
import type { SecretCipher } from '@cat-factory/kernel';
|
|
4
4
|
import type { EnvironmentHandle } from '@cat-factory/kernel';
|
|
5
5
|
import type { EnvironmentConnectionService } from './EnvironmentConnectionService.js';
|
|
6
6
|
import type { ProvisioningLogRecorder } from '../provisioning-logs/ProvisioningLogService.js';
|
|
7
|
+
/**
|
|
8
|
+
* Notified after a teardown ATTEMPT has been recorded in the provisioning log, whether it
|
|
9
|
+
* succeeded or failed. Its one consumer today is the run's PR verification report, whose
|
|
10
|
+
* environment-lifecycle proof cannot be closed by the run itself: the TTL sweep that reclaims the
|
|
11
|
+
* environment fires long after the last step settled, so without this edge the PR says "still
|
|
12
|
+
* live" about an environment the platform destroyed on schedule.
|
|
13
|
+
*
|
|
14
|
+
* The FAILURE edge matters as much as the success one, and for the same reason. A run that has
|
|
15
|
+
* settled has no step hook left to fire, so an environment the provider refuses to reclaim would
|
|
16
|
+
* otherwise sit on the PR as "nobody has torn this down yet" rather than as the thing an operator
|
|
17
|
+
* has to go and do: the same unreachable-leg hole one state over. The hook is therefore fired
|
|
18
|
+
* from the ONE place that records the attempt, so a future third teardown path cannot forget it.
|
|
19
|
+
*
|
|
20
|
+
* Strictly best-effort and swallowed by the service: reclaiming infrastructure must never
|
|
21
|
+
* depend on a downstream bookkeeping write.
|
|
22
|
+
*/
|
|
23
|
+
export type EnvironmentTeardownRecordedHook = (record: EnvironmentRecord, outcome: ProvisioningOutcome) => Promise<void>;
|
|
7
24
|
export interface EnvironmentTeardownServiceDependencies {
|
|
8
25
|
connectionService: EnvironmentConnectionService;
|
|
9
26
|
environmentRegistryRepository: EnvironmentRegistryRepository;
|
|
@@ -11,16 +28,34 @@ export interface EnvironmentTeardownServiceDependencies {
|
|
|
11
28
|
clock: Clock;
|
|
12
29
|
/** Best-effort provisioning-event log; absent ⇒ teardown is unchanged. */
|
|
13
30
|
provisioningLog?: ProvisioningLogRecorder;
|
|
31
|
+
/** Structured logger for the best-effort hook below; absent ⇒ its failures are unreported. */
|
|
32
|
+
logger?: Logger;
|
|
14
33
|
}
|
|
15
34
|
export declare class EnvironmentTeardownService {
|
|
16
35
|
private readonly deps;
|
|
36
|
+
private readonly log;
|
|
37
|
+
/**
|
|
38
|
+
* Late-bound because the engine that consumes it is constructed AFTER this service (it is
|
|
39
|
+
* injected into the provisioning service, which the engine takes). Same shape as
|
|
40
|
+
* `setInitiativeLoop` in the composition root, and for the same reason.
|
|
41
|
+
*/
|
|
42
|
+
private teardownRecordedHook;
|
|
17
43
|
constructor(deps: EnvironmentTeardownServiceDependencies);
|
|
44
|
+
/** Late-bind the teardown-recorded notification. Unset ⇒ nothing is notified. */
|
|
45
|
+
setTeardownRecordedHook(hook: EnvironmentTeardownRecordedHook | undefined): void;
|
|
18
46
|
/** Tear down a single environment and tombstone its record. */
|
|
19
47
|
teardown(workspaceId: string, id: string): Promise<EnvironmentHandle>;
|
|
20
48
|
/** Tear down every environment whose TTL has elapsed. Returns the count swept. */
|
|
21
49
|
sweepExpired(now: number): Promise<number>;
|
|
22
50
|
private teardownRecord;
|
|
23
|
-
|
|
51
|
+
/**
|
|
52
|
+
* Append the attempt to the provisioning log and then notify, in that order and in ONE place.
|
|
53
|
+
* The ordering is what makes the notification usable: its consumer reads the log back, so a
|
|
54
|
+
* hook fired before the row landed would report the state the edge exists to correct. Keeping
|
|
55
|
+
* both here is what makes the FAILURE edge structural rather than a line someone remembered to
|
|
56
|
+
* add beside the success one.
|
|
57
|
+
*/
|
|
58
|
+
private recordTeardownAttempt;
|
|
24
59
|
private decryptFields;
|
|
25
60
|
}
|
|
26
61
|
//# sourceMappingURL=EnvironmentTeardownService.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EnvironmentTeardownService.d.ts","sourceRoot":"","sources":["../../../src/modules/environments/EnvironmentTeardownService.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAA;
|
|
1
|
+
{"version":3,"file":"EnvironmentTeardownService.d.ts","sourceRoot":"","sources":["../../../src/modules/environments/EnvironmentTeardownService.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAA;AAC7E,OAAO,KAAK,EAAE,iBAAiB,EAAE,6BAA6B,EAAE,MAAM,qBAAqB,CAAA;AAC3F,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AACvD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAA;AAE5D,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,mCAAmC,CAAA;AAErF,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,gDAAgD,CAAA;AAO7F;;;;;;;;;;;;;;;GAeG;AACH,MAAM,MAAM,+BAA+B,GAAG,CAC5C,MAAM,EAAE,iBAAiB,EACzB,OAAO,EAAE,mBAAmB,KACzB,OAAO,CAAC,IAAI,CAAC,CAAA;AAElB,MAAM,WAAW,sCAAsC;IACrD,iBAAiB,EAAE,4BAA4B,CAAA;IAC/C,6BAA6B,EAAE,6BAA6B,CAAA;IAC5D,YAAY,EAAE,YAAY,CAAA;IAC1B,KAAK,EAAE,KAAK,CAAA;IACZ,0EAA0E;IAC1E,eAAe,CAAC,EAAE,uBAAuB,CAAA;IACzC,8FAA8F;IAC9F,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAED,qBAAa,0BAA0B;IASzB,OAAO,CAAC,QAAQ,CAAC,IAAI;IARjC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAQ;IAC5B;;;;OAIG;IACH,OAAO,CAAC,oBAAoB,CAA6C;IAEzE,YAA6B,IAAI,EAAE,sCAAsC,EAExE;IAED,iFAAiF;IACjF,uBAAuB,CAAC,IAAI,EAAE,+BAA+B,GAAG,SAAS,GAAG,IAAI,CAE/E;IAED,+DAA+D;IACzD,QAAQ,CAAC,WAAW,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAQ1E;IAED,kFAAkF;IAC5E,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAa/C;YAEa,cAAc;IAqC5B;;;;;;OAMG;YACW,qBAAqB;YA2BrB,aAAa;CAK5B"}
|
|
@@ -1,9 +1,21 @@
|
|
|
1
|
-
import { assertFound } from '@cat-factory/kernel';
|
|
1
|
+
import { assertFound, noopLogger, runBestEffort } from '@cat-factory/kernel';
|
|
2
2
|
import { recordToHandle } from './environments.logic.js';
|
|
3
3
|
export class EnvironmentTeardownService {
|
|
4
4
|
deps;
|
|
5
|
+
log;
|
|
6
|
+
/**
|
|
7
|
+
* Late-bound because the engine that consumes it is constructed AFTER this service (it is
|
|
8
|
+
* injected into the provisioning service, which the engine takes). Same shape as
|
|
9
|
+
* `setInitiativeLoop` in the composition root, and for the same reason.
|
|
10
|
+
*/
|
|
11
|
+
teardownRecordedHook;
|
|
5
12
|
constructor(deps) {
|
|
6
13
|
this.deps = deps;
|
|
14
|
+
this.log = deps.logger ?? noopLogger;
|
|
15
|
+
}
|
|
16
|
+
/** Late-bind the teardown-recorded notification. Unset ⇒ nothing is notified. */
|
|
17
|
+
setTeardownRecordedHook(hook) {
|
|
18
|
+
this.teardownRecordedHook = hook;
|
|
7
19
|
}
|
|
8
20
|
/** Tear down a single environment and tombstone its record. */
|
|
9
21
|
async teardown(workspaceId, id) {
|
|
@@ -49,14 +61,21 @@ export class EnvironmentTeardownService {
|
|
|
49
61
|
// Log the verbatim provider error before it propagates (the sweep swallows
|
|
50
62
|
// it; an on-demand teardown surfaces it). The local record is NOT tombstoned
|
|
51
63
|
// on a provider failure, matching the existing retry-next-pass behaviour.
|
|
52
|
-
await this.
|
|
64
|
+
await this.recordTeardownAttempt(record, 'failure', error instanceof Error ? error.message : String(error));
|
|
53
65
|
throw error;
|
|
54
66
|
}
|
|
55
67
|
}
|
|
56
68
|
await this.deps.environmentRegistryRepository.softDelete(record.workspaceId, record.id, this.deps.clock.now());
|
|
57
|
-
await this.
|
|
69
|
+
await this.recordTeardownAttempt(record, 'success', null);
|
|
58
70
|
}
|
|
59
|
-
|
|
71
|
+
/**
|
|
72
|
+
* Append the attempt to the provisioning log and then notify, in that order and in ONE place.
|
|
73
|
+
* The ordering is what makes the notification usable: its consumer reads the log back, so a
|
|
74
|
+
* hook fired before the row landed would report the state the edge exists to correct. Keeping
|
|
75
|
+
* both here is what makes the FAILURE edge structural rather than a line someone remembered to
|
|
76
|
+
* add beside the success one.
|
|
77
|
+
*/
|
|
78
|
+
async recordTeardownAttempt(record, outcome, error) {
|
|
60
79
|
await this.deps.provisioningLog?.record({
|
|
61
80
|
workspaceId: record.workspaceId,
|
|
62
81
|
subsystem: 'environment',
|
|
@@ -69,6 +88,15 @@ export class EnvironmentTeardownService {
|
|
|
69
88
|
error,
|
|
70
89
|
detail: null,
|
|
71
90
|
});
|
|
91
|
+
const hook = this.teardownRecordedHook;
|
|
92
|
+
if (!hook)
|
|
93
|
+
return;
|
|
94
|
+
await runBestEffort(this.log, 'environment.teardownRecordedHook', () => hook(record, outcome), {
|
|
95
|
+
workspaceId: record.workspaceId,
|
|
96
|
+
environmentId: record.id,
|
|
97
|
+
executionId: record.executionId,
|
|
98
|
+
outcome,
|
|
99
|
+
});
|
|
72
100
|
}
|
|
73
101
|
async decryptFields(cipher) {
|
|
74
102
|
if (!cipher)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EnvironmentTeardownService.js","sourceRoot":"","sources":["../../../src/modules/environments/EnvironmentTeardownService.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;
|
|
1
|
+
{"version":3,"file":"EnvironmentTeardownService.js","sourceRoot":"","sources":["../../../src/modules/environments/EnvironmentTeardownService.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AAE5E,OAAO,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAA;AAwCxD,MAAM,OAAO,0BAA0B;IASR,IAAI;IARhB,GAAG,CAAQ;IAC5B;;;;OAIG;IACK,oBAAoB,CAA6C;IAEzE,YAA6B,IAA4C;oBAA5C,IAAI;QAC/B,IAAI,CAAC,GAAG,GAAG,IAAI,CAAC,MAAM,IAAI,UAAU,CAAA;IACtC,CAAC;IAED,iFAAiF;IACjF,uBAAuB,CAAC,IAAiD;QACvE,IAAI,CAAC,oBAAoB,GAAG,IAAI,CAAA;IAClC,CAAC;IAED,+DAA+D;IAC/D,KAAK,CAAC,QAAQ,CAAC,WAAmB,EAAE,EAAU;QAC5C,MAAM,MAAM,GAAG,WAAW,CACxB,MAAM,IAAI,CAAC,IAAI,CAAC,6BAA6B,CAAC,GAAG,CAAC,WAAW,EAAE,EAAE,CAAC,EAClE,aAAa,EACb,EAAE,CACH,CAAA;QACD,MAAM,IAAI,CAAC,cAAc,CAAC,MAAM,CAAC,CAAA;QACjC,OAAO,cAAc,CAAC,EAAE,GAAG,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC,CAAA;IAC3D,CAAC;IAED,kFAAkF;IAClF,KAAK,CAAC,YAAY,CAAC,GAAW;QAC5B,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,6BAA6B,CAAC,WAAW,CAAC,GAAG,CAAC,CAAA;QAC9E,IAAI,KAAK,GAAG,CAAC,CAAA;QACb,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;YAC7B,IAAI,CAAC;gBACH,MAAM,IAAI,CAAC,cAAc,CAAC,MAAM,CAAC,CAAA;gBACjC,KAAK,EAAE,CAAA;YACT,CAAC;YAAC,MAAM,CAAC;gBACP,wEAAwE;gBACxE,yDAAyD;YAC3D,CAAC;QACH,CAAC;QACD,OAAO,KAAK,CAAA;IACd,CAAC;IAEO,KAAK,CAAC,cAAc,CAAC,MAAyB;QACpD,8FAA8F;QAC9F,+FAA+F;QAC/F,kCAAkC;QAClC,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,iBAAiB;aAC/C,wBAAwB,CAAC,MAAM,CAAC;aAChC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAA;QACpB,0EAA0E;QAC1E,IAAI,QAAQ,EAAE,CAAC;YACb,MAAM,eAAe,GAAG,MAAM,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC,qBAAqB,CAAC,CAAA;YAC9E,IAAI,CAAC;gBACH,MAAM,QAAQ,CAAC,QAAQ,CAAC,QAAQ,CAAC;oBAC/B,QAAQ,EAAE,QAAQ,CAAC,QAAQ;oBAC3B,UAAU,EAAE,MAAM,CAAC,UAAU;oBAC7B,eAAe;oBACf,aAAa,EAAE,QAAQ,CAAC,aAAa;iBACtC,CAAC,CAAA;YACJ,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,2EAA2E;gBAC3E,6EAA6E;gBAC7E,0EAA0E;gBAC1E,MAAM,IAAI,CAAC,qBAAqB,CAC9B,MAAM,EACN,SAAS,EACT,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CACvD,CAAA;gBACD,MAAM,KAAK,CAAA;YACb,CAAC;QACH,CAAC;QACD,MAAM,IAAI,CAAC,IAAI,CAAC,6BAA6B,CAAC,UAAU,CACtD,MAAM,CAAC,WAAW,EAClB,MAAM,CAAC,EAAE,EACT,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,CACtB,CAAA;QACD,MAAM,IAAI,CAAC,qBAAqB,CAAC,MAAM,EAAE,SAAS,EAAE,IAAI,CAAC,CAAA;IAC3D,CAAC;IAED;;;;;;OAMG;IACK,KAAK,CAAC,qBAAqB,CACjC,MAAyB,EACzB,OAA4B,EAC5B,KAAoB;QAEpB,MAAM,IAAI,CAAC,IAAI,CAAC,eAAe,EAAE,MAAM,CAAC;YACtC,WAAW,EAAE,MAAM,CAAC,WAAW;YAC/B,SAAS,EAAE,aAAa;YACxB,SAAS,EAAE,UAAU;YACrB,QAAQ,EAAE,MAAM,CAAC,EAAE;YACnB,UAAU,EAAE,MAAM,CAAC,UAAU;YAC7B,OAAO,EAAE,MAAM,CAAC,OAAO;YACvB,WAAW,EAAE,MAAM,CAAC,WAAW;YAC/B,OAAO;YACP,KAAK;YACL,MAAM,EAAE,IAAI;SACb,CAAC,CAAA;QACF,MAAM,IAAI,GAAG,IAAI,CAAC,oBAAoB,CAAA;QACtC,IAAI,CAAC,IAAI;YAAE,OAAM;QACjB,MAAM,aAAa,CAAC,IAAI,CAAC,GAAG,EAAE,kCAAkC,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE;YAC7F,WAAW,EAAE,MAAM,CAAC,WAAW;YAC/B,aAAa,EAAE,MAAM,CAAC,EAAE;YACxB,WAAW,EAAE,MAAM,CAAC,WAAW;YAC/B,OAAO;SACR,CAAC,CAAA;IACJ,CAAC;IAEO,KAAK,CAAC,aAAa,CAAC,MAAqB;QAC/C,IAAI,CAAC,MAAM;YAAE,OAAO,EAAE,CAAA;QACtB,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAA;QACvE,OAAO,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAE,MAAiC,CAAC,CAAC,CAAC,EAAE,CAAA;IACvF,CAAC;CACF"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cat-factory/integrations",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.120.0",
|
|
4
4
|
"description": "External-system integration domain logic for the Agent Architecture Board (GitHub, documents, tasks, environments, runners).",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -27,15 +27,15 @@
|
|
|
27
27
|
"ai": "^7.0.47",
|
|
28
28
|
"p-map": "^7.0.6",
|
|
29
29
|
"yaml": "^2.9.0",
|
|
30
|
-
"@cat-factory/contracts": "0.
|
|
31
|
-
"@cat-factory/kernel": "0.
|
|
30
|
+
"@cat-factory/contracts": "0.218.0",
|
|
31
|
+
"@cat-factory/kernel": "0.221.0"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
34
34
|
"typescript": "7.0.2",
|
|
35
35
|
"undici": "^8.9.0",
|
|
36
36
|
"valibot": "^1.4.2",
|
|
37
37
|
"vitest": "^4.1.10",
|
|
38
|
-
"@cat-factory/caching": "0.13.
|
|
38
|
+
"@cat-factory/caching": "0.13.6"
|
|
39
39
|
},
|
|
40
40
|
"scripts": {
|
|
41
41
|
"build": "tsc -b tsconfig.build.json",
|