@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 CHANGED
@@ -13,7 +13,7 @@
13
13
  ## Подключение
14
14
 
15
15
  ```sh
16
- pnpm add --save-exact @casecore/release-agent@0.1.6
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.equal(fact.kind, 'failed_before_installation');
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
- validateOriginalDelivery(fact.delivery, command.expectedSource.sourceCommit);
44
- assert.equal(fact.delivery.requestedAt, command.requestedAt);
45
- assert.equal(fact.delivery.deadlineAt, command.deadlineAt);
46
- if (delivery) assert.deepEqual(fact.delivery, delivery);
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
- await bounded(
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
- if (settled) await notifyPublicationTerminal(producer, item, run);
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
  }
@@ -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 PublicationFailureFact {
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,
@@ -1,29 +1,29 @@
1
1
  {
2
2
  "schema": 1,
3
3
  "package": "@casecore/release-agent",
4
- "version": "0.1.6",
5
- "sourceCommit": "70442860e0f89db88c78142c6f6a7e8073414af5",
4
+ "version": "0.1.7",
5
+ "sourceCommit": "517e8581d9e651cf4188c084e5a65e95b2e5bc82",
6
6
  "sourceVerified": true,
7
7
  "sourceDirty": false,
8
8
  "buildKind": "release",
9
- "contentDigest": "sha256:a41148b8adf594a5da507b1f8495b5b8c63e32b514d815c7bbce2634b0e6fb89",
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:76bda5e1dc376bb7033d8fdbddb0afa85c7c68bbe028aea078ffceaaa35ec75e",
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:9ec7c5e4ae6add8c0b64644673c3243946175fb5cc2dafcd5f1fdee2c6485ffd",
21
- "src/producer-publication-failure.mjs": "sha256:9db4effbeae8287070e3c27133abecf2bf435a8c6917c3162f0efd3189ea9445",
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:d5a9fca0bcb14f8c757d7219fcb8f2d39a2bf7af05c9cce3b8f654fa3060552c",
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:f64ae58d8e5984caea148a97e2cdf7d52a77bc472fb17ec75b5af1350f763fcc",
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"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@casecore/release-agent",
3
- "version": "0.1.6",
3
+ "version": "0.1.7",
4
4
  "description": "Общий движок поставки CaseCore с адаптерами продуктов",
5
5
  "license": "UNLICENSED",
6
6
  "repository": {