@casecore/release-agent 0.1.6 → 0.1.7
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/README.md +10 -1
- package/dist/index.mjs +1 -1
- package/dist/producer-journal.mjs +3 -0
- package/dist/producer-publication-failure.mjs +54 -9
- package/dist/producer.d.ts +29 -3
- package/dist/producer.mjs +1 -1
- package/dist/provenance.json +8 -8
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
## Подключение
|
|
14
14
|
|
|
15
15
|
```sh
|
|
16
|
-
pnpm add --save-exact @casecore/release-agent@0.1.
|
|
16
|
+
pnpm add --save-exact @casecore/release-agent@0.1.7
|
|
17
17
|
```
|
|
18
18
|
|
|
19
19
|
В проекте фиксируются точная версия и `pnpm-lock.yaml`. Поддерживается Node.js
|
|
@@ -323,3 +323,12 @@ production readback; новую установку или продление с
|
|
|
323
323
|
принятого через существующий report, адаптер может предоставить
|
|
324
324
|
`readTerminalReceipt(commandId)`. Неизвестный результат возвращает 404;
|
|
325
325
|
неподтверждённые ошибки установки не получают такой результат.
|
|
326
|
+
|
|
327
|
+
Отказ до push использует тот же guarded recovery с `kind: failed_before_source_push`,
|
|
328
|
+
`stage: source_ready` и `sourceExecution` вместо GitHub `delivery`. Исходная sealed
|
|
329
|
+
попытка имеет `schema: 1`, `kind: source_not_pushed`, `id`, `candidateId`,
|
|
330
|
+
`sourceCommit`, `requestedAt`, `deadlineAt`, `policy: immediate`. Обязательный
|
|
331
|
+
`verifyPublicationSourceFailure` подтверждает эту границу под удерживаемым guard.
|
|
332
|
+
Любой bound delivery, артефакт или начатая следующая стадия отказывают. ACK сохраняет
|
|
333
|
+
предыдущую рабочую установку и снимает блокировку без создания GitHub-статуса или
|
|
334
|
+
publication-terminal receipt. Истёкший срок не продлевается.
|
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export * from './pipeline.mjs';
|
|
2
2
|
export * from './source-store.mjs';
|
|
3
3
|
export * from './producer.mjs';
|
|
4
|
-
export { publicationFailureProofDigest } from './producer-publication-failure.mjs';
|
|
4
|
+
export { publicationFailureProofDigest, validateSourceRecoveryExecution } from './producer-publication-failure.mjs';
|
|
5
5
|
export { createProtectedJsonStore } from './journal.mjs';
|
|
@@ -100,6 +100,9 @@ export function validProducerState(state, installationKey, sourcePolicy) {
|
|
|
100
100
|
if (reconciliation) {
|
|
101
101
|
assert.equal(state.commands[run.commandId].command.action, 'publish');
|
|
102
102
|
assert.ok(!state.commands[run.commandId].artifactIdentity);
|
|
103
|
+
if (reconciliation.fact?.kind === 'failed_before_source_push')
|
|
104
|
+
for (const stage of run.stages.filter((stage) => stage.id !== 'source_ready'))
|
|
105
|
+
assert.ok(stage.status === 'pending' && stage.startedAt === null);
|
|
103
106
|
if (reconciliation.state === 'settled') {
|
|
104
107
|
assert.equal(run.status, 'failed');
|
|
105
108
|
assert.equal(run.recoveryState, 'verified');
|
|
@@ -4,6 +4,8 @@ import {
|
|
|
4
4
|
hash,
|
|
5
5
|
keys,
|
|
6
6
|
timestamp,
|
|
7
|
+
uuid,
|
|
8
|
+
boundedText,
|
|
7
9
|
completeIdentity,
|
|
8
10
|
validateReadback,
|
|
9
11
|
} from './protocol-internals.mjs';
|
|
@@ -18,14 +20,36 @@ export function publicationFailureProofDigest(fact, baselineReadback) {
|
|
|
18
20
|
return `sha256:${hash(canonical({ fact, baselineReadback }))}`;
|
|
19
21
|
}
|
|
20
22
|
|
|
23
|
+
export function validateSourceRecoveryExecution(value, command) {
|
|
24
|
+
keys(value, ['schema', 'kind', 'id', 'candidateId', 'sourceCommit', 'requestedAt', 'deadlineAt', 'policy']);
|
|
25
|
+
assert.equal(value.schema, 1);
|
|
26
|
+
assert.equal(value.kind, 'source_not_pushed');
|
|
27
|
+
assert.match(value.id, uuid);
|
|
28
|
+
assert.match(value.sourceCommit, /^[a-f0-9]{40}$/);
|
|
29
|
+
boundedText(value.candidateId);
|
|
30
|
+
assert.equal(value.policy, 'immediate');
|
|
31
|
+
timestamp(value.requestedAt);
|
|
32
|
+
timestamp(value.deadlineAt);
|
|
33
|
+
assert.equal(Date.parse(value.deadlineAt) - Date.parse(value.requestedAt), 900000);
|
|
34
|
+
if (command) {
|
|
35
|
+
assert.equal(command.action, 'publish');
|
|
36
|
+
assert.equal(value.candidateId, command.candidateId);
|
|
37
|
+
assert.equal(value.sourceCommit, command.expectedSource.sourceCommit);
|
|
38
|
+
assert.equal(value.requestedAt, command.requestedAt);
|
|
39
|
+
assert.equal(value.deadlineAt, command.deadlineAt);
|
|
40
|
+
}
|
|
41
|
+
return value;
|
|
42
|
+
}
|
|
43
|
+
|
|
21
44
|
export function validatePublicationFailureFact(fact, command, delivery) {
|
|
45
|
+
const beforePush = fact?.kind === 'failed_before_source_push';
|
|
22
46
|
keys(fact, [
|
|
23
47
|
'schema',
|
|
24
48
|
'kind',
|
|
25
49
|
'commandId',
|
|
26
50
|
'installationKey',
|
|
27
51
|
'expectedSource',
|
|
28
|
-
'delivery',
|
|
52
|
+
beforePush ? 'sourceExecution' : 'delivery',
|
|
29
53
|
'observedAt',
|
|
30
54
|
'stage',
|
|
31
55
|
'authenticated',
|
|
@@ -35,15 +59,21 @@ export function validatePublicationFailureFact(fact, command, delivery) {
|
|
|
35
59
|
'installationNotStarted',
|
|
36
60
|
]);
|
|
37
61
|
assert.equal(fact.schema, 1);
|
|
38
|
-
assert.
|
|
62
|
+
assert.ok(beforePush || fact.kind === 'failed_before_installation');
|
|
39
63
|
assert.equal(fact.commandId, command.commandId);
|
|
40
64
|
assert.equal(fact.installationKey, command.installationKey);
|
|
41
65
|
keys(fact.expectedSource, ['repository', 'ref', 'sourceCommit']);
|
|
42
66
|
assert.equal(canonical(fact.expectedSource), canonical(command.expectedSource));
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
67
|
+
if (beforePush) {
|
|
68
|
+
assert.ok(delivery == null, 'Source failure cannot replace an admitted GitHub delivery');
|
|
69
|
+
validateSourceRecoveryExecution(fact.sourceExecution, command);
|
|
70
|
+
assert.equal(fact.stage, 'source_ready');
|
|
71
|
+
} else {
|
|
72
|
+
validateOriginalDelivery(fact.delivery, command.expectedSource.sourceCommit);
|
|
73
|
+
assert.equal(fact.delivery.requestedAt, command.requestedAt);
|
|
74
|
+
assert.equal(fact.delivery.deadlineAt, command.deadlineAt);
|
|
75
|
+
if (delivery) assert.deepEqual(fact.delivery, delivery);
|
|
76
|
+
}
|
|
47
77
|
timestamp(fact.observedAt);
|
|
48
78
|
assert.ok(stages.includes(fact.stage));
|
|
49
79
|
for (const field of [
|
|
@@ -209,7 +239,19 @@ export async function reconcilePublicationFailure(
|
|
|
209
239
|
const { observedAt: _newTime, ...newIdentity } = fact;
|
|
210
240
|
assert.deepEqual(newIdentity, oldIdentity, 'Publication failure reconciliation changed');
|
|
211
241
|
}
|
|
212
|
-
|
|
242
|
+
const beforePush = fact.kind === 'failed_before_source_push';
|
|
243
|
+
if (beforePush) {
|
|
244
|
+
assert.ok(!item.deliveryExecution && !item.artifactIdentity);
|
|
245
|
+
for (const stage of run.stages.filter((stage) => stage.id !== 'source_ready'))
|
|
246
|
+
assert.ok(stage.status === 'pending' && stage.startedAt === null,
|
|
247
|
+
'Source failure cannot erase a started downstream stage');
|
|
248
|
+
assert.equal(typeof producer.provider.verifyPublicationSourceFailure, 'function',
|
|
249
|
+
'Source failure boundary verifier required');
|
|
250
|
+
assert.equal(await bounded(producer.provider.verifyPublicationSourceFailure(
|
|
251
|
+
structuredClone(item.command), structuredClone(fact.sourceExecution),
|
|
252
|
+
{ signal: controller.signal, recoveryDeadlineAt }
|
|
253
|
+
)), true, 'Source failure boundary was not verified');
|
|
254
|
+
} else await bounded(
|
|
213
255
|
verifyPublicationDelivery(producer, item.command, fact.delivery, { fresh: false })
|
|
214
256
|
);
|
|
215
257
|
await bounded(guard.assertOwned());
|
|
@@ -218,7 +260,7 @@ export async function reconcilePublicationFailure(
|
|
|
218
260
|
assertFresh();
|
|
219
261
|
item.publicationFailureReconciliation.fact = fact;
|
|
220
262
|
item.publicationFailureReconciliation.baselineReadback = baselineReadback;
|
|
221
|
-
item.deliveryExecution = structuredClone(fact.delivery);
|
|
263
|
+
if (!beforePush) item.deliveryExecution = structuredClone(fact.delivery);
|
|
222
264
|
run.readback = structuredClone(baselineReadback);
|
|
223
265
|
await bounded(producer.save());
|
|
224
266
|
const proofDigest = publicationFailureProofDigest(fact, baselineReadback);
|
|
@@ -280,5 +322,8 @@ export async function reconcilePublicationFailure(
|
|
|
280
322
|
if (guard && !settled && !releasing && typeof guard.release === 'function') void cleanup(guard);
|
|
281
323
|
lease.release({ resolved: settled });
|
|
282
324
|
}
|
|
283
|
-
|
|
325
|
+
// Pre-push source recovery has no GitHub delivery or publication-terminal MAC.
|
|
326
|
+
// Its existing guarded acknowledgement is the sole settlement boundary.
|
|
327
|
+
if (settled && item.publicationFailureReconciliation.fact.kind !== 'failed_before_source_push')
|
|
328
|
+
await notifyPublicationTerminal(producer, item, run);
|
|
284
329
|
}
|
package/dist/producer.d.ts
CHANGED
|
@@ -132,6 +132,11 @@ export interface ReleaseProvider {
|
|
|
132
132
|
command: RunnerCommand,
|
|
133
133
|
context: ProviderContext & { delivery: OriginalDelivery | null; recoveryDeadlineAt: number }
|
|
134
134
|
): Promise<PublicationFailureGuard | null>;
|
|
135
|
+
verifyPublicationSourceFailure?(
|
|
136
|
+
command: RunnerCommand,
|
|
137
|
+
execution: SourceRecoveryExecution,
|
|
138
|
+
context: ProviderContext & { recoveryDeadlineAt: number }
|
|
139
|
+
): Promise<boolean>;
|
|
135
140
|
validatePublicationResume?(
|
|
136
141
|
command: RunnerCommand,
|
|
137
142
|
item: Record<string, unknown>
|
|
@@ -150,13 +155,25 @@ export interface ReleaseProvider {
|
|
|
150
155
|
): Promise<Record<string, unknown>>;
|
|
151
156
|
productionFailure?(fact: ProductionReadback): Error | undefined;
|
|
152
157
|
}
|
|
153
|
-
export interface
|
|
158
|
+
export interface SourceRecoveryExecution {
|
|
159
|
+
schema: 1;
|
|
160
|
+
kind: 'source_not_pushed';
|
|
161
|
+
id: string;
|
|
162
|
+
candidateId: string;
|
|
163
|
+
sourceCommit: string;
|
|
164
|
+
requestedAt: string;
|
|
165
|
+
deadlineAt: string;
|
|
166
|
+
policy: 'immediate';
|
|
167
|
+
}
|
|
168
|
+
export function validateSourceRecoveryExecution(
|
|
169
|
+
execution: unknown,
|
|
170
|
+
command?: RunnerCommand
|
|
171
|
+
): SourceRecoveryExecution;
|
|
172
|
+
interface PublicationFailureCommon {
|
|
154
173
|
schema: 1;
|
|
155
|
-
kind: 'failed_before_installation';
|
|
156
174
|
commandId: string;
|
|
157
175
|
installationKey: string;
|
|
158
176
|
expectedSource: { repository: string; ref: string; sourceCommit: string };
|
|
159
|
-
delivery: OriginalDelivery;
|
|
160
177
|
observedAt: string;
|
|
161
178
|
stage: 'source_ready' | 'ci' | 'build' | 'verify' | 'publish';
|
|
162
179
|
authenticated: true;
|
|
@@ -165,6 +182,15 @@ export interface PublicationFailureFact {
|
|
|
165
182
|
publicationResolved: true;
|
|
166
183
|
installationNotStarted: true;
|
|
167
184
|
}
|
|
185
|
+
export type PublicationFailureFact = PublicationFailureCommon &
|
|
186
|
+
(
|
|
187
|
+
| { kind: 'failed_before_installation'; delivery: OriginalDelivery }
|
|
188
|
+
| {
|
|
189
|
+
kind: 'failed_before_source_push';
|
|
190
|
+
sourceExecution: SourceRecoveryExecution;
|
|
191
|
+
stage: 'source_ready';
|
|
192
|
+
}
|
|
193
|
+
);
|
|
168
194
|
export interface PublicationFailureGuard {
|
|
169
195
|
fact: PublicationFailureFact;
|
|
170
196
|
baselineReadback: ProductionReadback;
|
package/dist/producer.mjs
CHANGED
|
@@ -9,7 +9,7 @@ export {
|
|
|
9
9
|
sealPublicationTerminalReceipt,
|
|
10
10
|
verifyPublicationTerminalReceipt,
|
|
11
11
|
} from './publication-terminal.mjs';
|
|
12
|
-
export { publicationFailureProofDigest } from './producer-publication-failure.mjs';
|
|
12
|
+
export { publicationFailureProofDigest, validateSourceRecoveryExecution } from './producer-publication-failure.mjs';
|
|
13
13
|
export {
|
|
14
14
|
sameIdentity,
|
|
15
15
|
validateExpectedSource,
|
package/dist/provenance.json
CHANGED
|
@@ -1,29 +1,29 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schema": 1,
|
|
3
3
|
"package": "@casecore/release-agent",
|
|
4
|
-
"version": "0.1.
|
|
5
|
-
"sourceCommit": "
|
|
4
|
+
"version": "0.1.7",
|
|
5
|
+
"sourceCommit": "517e8581d9e651cf4188c084e5a65e95b2e5bc82",
|
|
6
6
|
"sourceVerified": true,
|
|
7
7
|
"sourceDirty": false,
|
|
8
8
|
"buildKind": "release",
|
|
9
|
-
"contentDigest": "sha256:
|
|
9
|
+
"contentDigest": "sha256:bd08880215c8d1203050961fa9971925b212d463354b847d1574d92c4720c524",
|
|
10
10
|
"files": {
|
|
11
11
|
"src/configuration-hook.mjs": "sha256:1171bf982cbf7cb0092f63b633ef0b0a0947d8ed1106f88c9175c8add33d81b1",
|
|
12
12
|
"src/exclusive-operation.mjs": "sha256:d5c2718a2a4228d09559e914360329383ae276f034bb468c52642a4bd6aa0d99",
|
|
13
13
|
"src/identity.mjs": "sha256:253021058fc79232d83f3ecba6f1023fd046f27aabdf9c129a103d3c9ccb07f5",
|
|
14
|
-
"src/index.mjs": "sha256:
|
|
14
|
+
"src/index.mjs": "sha256:2df871905b31d8cb287dd0700765be6dd9d76fbb91d25b9275f37b07ab4669b5",
|
|
15
15
|
"src/journal.mjs": "sha256:35a2620d56084ec1aa164e09d77b8e77096413d9572f7b601da8ef7149b996bd",
|
|
16
16
|
"src/pipeline.mjs": "sha256:7a2d39d235ce7160441d177200389c5f3eae1cc494ec36082a23e8d64a0abe3f",
|
|
17
17
|
"src/producer-admission.mjs": "sha256:03d868141daf92351b3e1a4e70997c87fc39044624644536a374aa86d108d12d",
|
|
18
18
|
"src/producer-delivery.mjs": "sha256:04439a95afcb52d879cc83b99fa1d7716f4dff84bb7c07f88d31f534799e76a8",
|
|
19
19
|
"src/producer-execution.mjs": "sha256:e78724fa2f2fd4946ae8103bb5165b363eb968fab42add4fed572155f51871d9",
|
|
20
|
-
"src/producer-journal.mjs": "sha256:
|
|
21
|
-
"src/producer-publication-failure.mjs": "sha256:
|
|
20
|
+
"src/producer-journal.mjs": "sha256:d257e2042d92b828de44247b6affbe0b06e00263a1bb1939a1faa032daeaca9e",
|
|
21
|
+
"src/producer-publication-failure.mjs": "sha256:f077e62424cffb5123a81444e872fb2e2dec9b71dec89be7ca00b37227f8a648",
|
|
22
22
|
"src/producer-publication.mjs": "sha256:c09fa07b37507093b71bbaff3af9df8ec2331c0cba24a4b936d247e9b35257f7",
|
|
23
23
|
"src/producer-server.mjs": "sha256:fc67ceced78239a1d159ff1af82b0061a407891523ad2a1a9139a3d4d158619c",
|
|
24
24
|
"src/producer-state.mjs": "sha256:61d6a914c268f94790c459562436bfe95efd07865ca1052b0b58760d9087d535",
|
|
25
25
|
"src/producer-support.mjs": "sha256:0a10f6fa50cd8e729ba432640d0845004488ead2bcc1c9383f02548037f83d66",
|
|
26
|
-
"src/producer.mjs": "sha256:
|
|
26
|
+
"src/producer.mjs": "sha256:50addd14e7ae0ae7fb41fb5a4c733f1876d0551befb6db909edeed6cb5f44c1a",
|
|
27
27
|
"src/protocol-internals.mjs": "sha256:d08a0c72079d091792c5327adafac5ffc38ea20138a6c44b88879e7157a890b3",
|
|
28
28
|
"src/protocol.mjs": "sha256:1fe135a2e60922b517886a392c2b0c2250b773abda8b1a1fcd9af86df2be6dae",
|
|
29
29
|
"src/publication-terminal-receipt.mjs": "sha256:b4ecc620f7e4325ec9d42652b8f1620f609fad65b1400cd2810a0a2f7e7b6794",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"types/index.d.ts": "sha256:92ef7b5600f05d52dcb646fc3e4d2b4944ead52ff1c9ee23bb9f4659bebf05a6",
|
|
37
37
|
"types/journal.d.ts": "sha256:79beb61af50b00400588a87426a7f5b38bf38d4b8224284dc6e0631378aa244e",
|
|
38
38
|
"types/pipeline.d.ts": "sha256:c9135e25fb852377c32a16ce781405393c5857886e3f77754b715747202c70a9",
|
|
39
|
-
"types/producer.d.ts": "sha256:
|
|
39
|
+
"types/producer.d.ts": "sha256:16cc19bd45fbf0cec564c2d6ec5d5ad00f3f866c89eb8af94e6b5c33ed1be22f",
|
|
40
40
|
"types/protocol.d.ts": "sha256:02c06986c81bfc4214169f62da8e0566704b71a5add58434dfca92c673fad4ce",
|
|
41
41
|
"types/source-store.d.ts": "sha256:ce2086dd682e8f57bc5e1581b411ec9fe1cf1cfba8dccafe38c22d102c2b9904",
|
|
42
42
|
"types/types.d.ts": "sha256:cdc6664854bffc371a3da45f29bf1b31f14fd61b66e8f8bae8309dd836f6de6e"
|