@aldus-runtime/release 0.2.0-next.20 → 0.2.0-next.21

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/adapter.d.ts CHANGED
@@ -35,6 +35,21 @@ export type AdapterOutcome = {
35
35
  remoteId?: string;
36
36
  /** Address of the released item, where one is meaningful. */
37
37
  remoteUrl?: string;
38
+ /**
39
+ * Something an operator needs to know about a success (§20; #169 item 4).
40
+ *
41
+ * `failed` has `message` and `pending` has `message?`; `succeeded` had nowhere to put a
42
+ * sentence at all. An adapter that removed a marker from one video of several, or that found
43
+ * no marker and therefore removed nothing, succeeded — and had to discard the only part an
44
+ * operator would have wanted.
45
+ *
46
+ * **Not a channel for a qualified absence.** An adapter that cannot establish remote state
47
+ * returns {@link cannotEstablish} from `lookup`; using this to footnote a fabricated
48
+ * `exists: false` would be the same false record with better documentation (#169 item 3).
49
+ *
50
+ * Already redacted by the adapter (§19.2), like every other adapter-supplied string.
51
+ */
52
+ note?: string;
38
53
  } | {
39
54
  status: "failed";
40
55
  /** Operator-facing reason, already redacted by the adapter (contract §19.2). */
@@ -53,13 +68,51 @@ export type AdapterOutcome = {
53
68
  status: "pending";
54
69
  message?: string;
55
70
  };
71
+ /** @see CannotEstablish */
72
+ declare const CANNOT_ESTABLISH: unique symbol;
73
+ /**
74
+ * The destination could not be queried, so whether it holds the operation is unknown (#169).
75
+ *
76
+ * Distinct from `exists: false` in the way that matters: **`exists: false` is the answer only a
77
+ * completed search can give.** Before this, an adapter that could not establish the answer had two
78
+ * options — return `false` and assert a search nobody performed, or throw and abort the whole
79
+ * reconciliation pass before a single operation executed. Both were forced, and adopters took the
80
+ * first.
81
+ *
82
+ * Minted by {@link cannotEstablish} and not assemblable, because a state that suppresses a
83
+ * publish-safety check must not be reachable by writing an object literal.
84
+ */
85
+ export interface CannotEstablish {
86
+ readonly [CANNOT_ESTABLISH]: "cannot_establish";
87
+ /**
88
+ * Why the answer could not be established, for the operator (§20).
89
+ *
90
+ * **Required.** An adapter that cannot establish something and cannot say why has produced a
91
+ * finding nobody can act on — a rate limit, a permission, and a destination that does not retain
92
+ * what would identify the operation are three different problems with three different responses.
93
+ */
94
+ readonly reason: string;
95
+ }
96
+ /**
97
+ * Declare that the destination could not be queried (§17, §19.1; #169).
98
+ *
99
+ * @throws {AldusError} when the reason is empty.
100
+ */
101
+ export declare function cannotEstablish(reason: string): CannotEstablish;
102
+ /** Whether a remote state is an issued {@link CannotEstablish}. Membership, not shape. */
103
+ export declare function isCannotEstablish(state: RemoteState): state is CannotEstablish;
56
104
  /** What reconciliation found at the destination. */
57
- export interface RemoteState {
58
- /** Whether the destination holds the result of this operation. */
105
+ export type RemoteState = {
106
+ /**
107
+ * Whether the destination holds the result of this operation.
108
+ *
109
+ * `false` asserts a **completed search**. An adapter that did not or could not search
110
+ * returns {@link cannotEstablish} instead.
111
+ */
59
112
  exists: boolean;
60
113
  remoteId?: string;
61
114
  remoteUrl?: string;
62
- }
115
+ } | CannotEstablish;
63
116
  /**
64
117
  * An adopter's implementation for one destination.
65
118
  *
@@ -97,14 +150,29 @@ export declare class AdapterRegistry {
97
150
  */
98
151
  require(destination: string): ReleaseAdapter;
99
152
  }
100
- /** Scripted behaviour for {@link RecordingReleaseAdapter}. */
101
153
  export interface RecordingAdapterOptions {
102
154
  /** Outcome per `operationId`. Anything unlisted succeeds. */
103
155
  outcomes?: Readonly<Record<string, AdapterOutcome>>;
104
- /** Remote state per idempotency key, as reconciliation would find it. */
105
- remote?: Readonly<Record<string, RemoteState>>;
156
+ /**
157
+ * Remote state per idempotency key, as reconciliation would find it.
158
+ *
159
+ * A plain object or a `Map`, because the field it becomes is a `Map` and seeding it with one is
160
+ * the natural mistake. `Object.entries(aMap)` is `[]`, so a `Map` used to seed **nothing**, with
161
+ * no error — and every lookup then fell through to `{ exists: false }`, which reads as a
162
+ * completed search finding nothing. The same trap as an adapter returning `{ present: false }`:
163
+ * an input accepted quietly and answered as though nothing was there.
164
+ */
165
+ remote?: Readonly<Record<string, RemoteState>> | ReadonlyMap<string, RemoteState>;
106
166
  /** Omit `lookup` entirely, modelling a destination that cannot be queried. */
107
167
  withoutLookup?: boolean;
168
+ /**
169
+ * Make `lookup` throw for these `operationId`s, modelling an unanticipated query failure.
170
+ *
171
+ * Distinct from returning {@link cannotEstablish}: an adapter that knows it cannot answer says
172
+ * so, and this is the adapter that does not know — a quota error, a dropped connection. The
173
+ * executor has to survive both (#169).
174
+ */
175
+ lookupThrowsFor?: readonly string[];
108
176
  }
109
177
  /**
110
178
  * A test double that records what it was asked to do.
@@ -137,4 +205,5 @@ export declare class RecordingReleaseAdapter implements ReleaseAdapter {
137
205
  /** How many times an operation was actually sent to the destination. */
138
206
  executionCount(operationId: string): number;
139
207
  }
208
+ export {};
140
209
  //# sourceMappingURL=adapter.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"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"}
package/dist/adapter.js CHANGED
@@ -13,6 +13,34 @@
13
13
  * double.
14
14
  */
15
15
  import { ReleaseErrorCodes, releaseError } from "./errors.js";
16
+ /**
17
+ * Authorities this process minted. A cast cannot manufacture membership.
18
+ *
19
+ * The same runtime proof `SynthesisPermit` uses, and for the same reason: the brand above is a
20
+ * phantom, absent at runtime, so a `RemoteState` narrowing that trusted it would trust a type
21
+ * assertion. #170 has the longer version of this argument — a check that assumes the type it is
22
+ * checking is not a check.
23
+ */
24
+ const ISSUED_CANNOT_ESTABLISH = new WeakSet();
25
+ /**
26
+ * Declare that the destination could not be queried (§17, §19.1; #169).
27
+ *
28
+ * @throws {AldusError} when the reason is empty.
29
+ */
30
+ export function cannotEstablish(reason) {
31
+ if (typeof reason !== "string" || reason.trim().length === 0) {
32
+ throw releaseError(ReleaseErrorCodes.OPERATION_INVALID, "An unestablished remote state must say why. A rate limit, a permission problem and a " +
33
+ "destination that does not retain what identifies the operation call for three different " +
34
+ "responses, and an operator cannot tell them apart from the absence of an answer (§17).", { category: "validation", retryable: false, details: {} });
35
+ }
36
+ const state = { reason };
37
+ ISSUED_CANNOT_ESTABLISH.add(state);
38
+ return state;
39
+ }
40
+ /** Whether a remote state is an issued {@link CannotEstablish}. Membership, not shape. */
41
+ export function isCannotEstablish(state) {
42
+ return typeof state === "object" && state !== null && ISSUED_CANNOT_ESTABLISH.has(state);
43
+ }
16
44
  /** Adapters by destination. */
17
45
  export class AdapterRegistry {
18
46
  #byDestination = new Map();
@@ -43,6 +71,27 @@ export class AdapterRegistry {
43
71
  return adapter;
44
72
  }
45
73
  }
74
+ /** Scripted behaviour for {@link RecordingReleaseAdapter}. */
75
+ /**
76
+ * Build the remote-state map from either accepted shape, and refuse anything else.
77
+ *
78
+ * Refusing rather than tolerating, because the failure this replaces was silence: a mis-seeded
79
+ * harness answered every lookup as a completed search finding nothing, and a test written against
80
+ * it would assert the wrong behaviour and pass. A double that lies quietly is worse than one that
81
+ * will not start.
82
+ */
83
+ function seedRemote(remote) {
84
+ if (remote === undefined)
85
+ return new Map();
86
+ if (remote instanceof Map)
87
+ return new Map(remote);
88
+ if (typeof remote === "object" && remote !== null && !Array.isArray(remote)) {
89
+ return new Map(Object.entries(remote));
90
+ }
91
+ throw releaseError(ReleaseErrorCodes.OPERATION_INVALID, "A recording adapter's `remote` seed must be a plain object or a Map keyed by idempotency " +
92
+ "key. Anything else seeds nothing, and every lookup then answers as a completed search " +
93
+ "that found nothing — which is the defect this double is used to test for.", { category: "validation", retryable: false, details: {} });
94
+ }
46
95
  /**
47
96
  * A test double that records what it was asked to do.
48
97
  *
@@ -72,10 +121,13 @@ export class RecordingReleaseAdapter {
72
121
  constructor(destination, options = {}) {
73
122
  this.destination = destination;
74
123
  this.#options = options;
75
- this.remote = new Map(Object.entries(options.remote ?? {}));
124
+ this.remote = seedRemote(options.remote);
76
125
  if (options.withoutLookup !== true) {
77
126
  this.lookup = (request) => {
78
127
  this.lookedUp.push(request);
128
+ if (options.lookupThrowsFor?.includes(request.operation.operationId) === true) {
129
+ return Promise.reject(new Error("destination query failed"));
130
+ }
79
131
  return Promise.resolve(this.remote.get(request.idempotencyKey) ?? { exists: false });
80
132
  };
81
133
  }
@@ -1 +1 @@
1
- {"version":3,"file":"adapter.js","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"adapter.js","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"bundle.d.ts","sourceRoot":"","sources":["../src/bundle.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAKH,OAAO,KAAK,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAE/F;;;;;;GAMG;AACH,MAAM,WAAW,aAAa;IAC5B,0EAA0E;IAC1E,QAAQ,EAAE,MAAM,CAAC;IACjB,iDAAiD;IACjD,KAAK,EAAE,MAAM,CAAC;IACd,0EAA0E;IAC1E,SAAS,EAAE,MAAM,CAAC;IAClB,sEAAsE;IACtE,QAAQ,EAAE,SAAS,iBAAiB,EAAE,CAAC;IACvC,6FAA6F;IAC7F,UAAU,EAAE,SAAS,mBAAmB,EAAE,CAAC;CAC5C;AAED,uEAAuE;AACvE,wBAAgB,YAAY,CAAC,MAAM,EAAE,aAAa,GAAG,SAAS,gBAAgB,EAAE,CAE/E;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,aAAa,GAAG,IAAI,CA2B7D;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,gBAAgB,GAAG,MAAM,CAQxE"}
1
+ {"version":3,"file":"bundle.d.ts","sourceRoot":"","sources":["../src/bundle.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAKH,OAAO,KAAK,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAE/F;;;;;;GAMG;AACH,MAAM,WAAW,aAAa;IAC5B,0EAA0E;IAC1E,QAAQ,EAAE,MAAM,CAAC;IACjB,iDAAiD;IACjD,KAAK,EAAE,MAAM,CAAC;IACd,0EAA0E;IAC1E,SAAS,EAAE,MAAM,CAAC;IAClB,sEAAsE;IACtE,QAAQ,EAAE,SAAS,iBAAiB,EAAE,CAAC;IACvC,6FAA6F;IAC7F,UAAU,EAAE,SAAS,mBAAmB,EAAE,CAAC;CAC5C;AAED,uEAAuE;AACvE,wBAAgB,YAAY,CAAC,MAAM,EAAE,aAAa,GAAG,SAAS,gBAAgB,EAAE,CAE/E;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,aAAa,GAAG,IAAI,CAiE7D;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,gBAAgB,GAAG,MAAM,CAQxE"}
package/dist/bundle.js CHANGED
@@ -40,6 +40,38 @@ export function assertBundleValid(bundle) {
40
40
  });
41
41
  }
42
42
  seen.add(operation.operationId);
43
+ // Checked at runtime, because the type is not the enforcement. `repeatable()` refuses an empty
44
+ // reason and nothing stops a caller writing `{ reason: "" } as RepeatableDeclaration` — and
45
+ // the code that will assemble operations from configuration is exactly the code that does
46
+ // that. Without this the operation was treated as repeatable and the warning ended in a bare
47
+ // colon where the justification should be.
48
+ //
49
+ // Higher stakes than the usual version of this argument: the declaration licenses performing
50
+ // an external effect more than once, and §13.4 binds a release approval to the bundle, so a
51
+ // blank reason is an approval of nothing.
52
+ //
53
+ // The **shape** as well as the emptiness. The first version read `repeatable.reason.trim()`,
54
+ // which assumes `reason` is a string — the assumption this check exists because it cannot
55
+ // make. `repeatable: true` is the single most likely thing someone writes in a config file
56
+ // when they mean this, and it threw a bare `TypeError` with no code, no bundle or operation
57
+ // id, and no sentence saying what was wrong. Fail-closed, so nothing unsafe happened; what was
58
+ // lost was the refusal, arriving from exactly the source the paragraph above names.
59
+ const declaration = operation.repeatable;
60
+ if (declaration !== undefined) {
61
+ const reason = typeof declaration === "object" && declaration !== null
62
+ ? declaration.reason
63
+ : undefined;
64
+ if (typeof reason !== "string" || reason.trim().length === 0) {
65
+ throw releaseError(ReleaseErrorCodes.OPERATION_INVALID, `Release bundle "${bundle.bundleId}" declares "${operation.operationId}" safe to ` +
66
+ "repeat without a usable reason. Repeating an external effect is something an " +
67
+ "approver accepts, and neither a blank justification nor a value that is not a " +
68
+ "declaration is something anyone can approve. Build it with `repeatable(reason)` " +
69
+ "(contract §13.4, §17).", {
70
+ category: "validation",
71
+ details: { bundleId: bundle.bundleId, operationId: operation.operationId },
72
+ });
73
+ }
74
+ }
43
75
  }
44
76
  }
45
77
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"bundle.js","sourceRoot":"","sources":["../src/bundle.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC,OAAO,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAuB9D,uEAAuE;AACvE,MAAM,UAAU,YAAY,CAAC,MAAqB;IAChD,OAAO,CAAC,GAAG,MAAM,CAAC,QAAQ,EAAE,GAAG,MAAM,CAAC,UAAU,CAAC,CAAC;AACpD,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,iBAAiB,CAAC,MAAqB;IACrD,MAAM,UAAU,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC;IACxC,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC5B,MAAM,YAAY,CAChB,iBAAiB,CAAC,YAAY,EAC9B,mBAAmB,MAAM,CAAC,QAAQ,yDAAyD;YACzF,8CAA8C,EAChD,EAAE,QAAQ,EAAE,YAAY,EAAE,OAAO,EAAE,EAAE,QAAQ,EAAE,MAAM,CAAC,QAAQ,EAAE,EAAE,CACnE,CAAC;IACJ,CAAC;IAED,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;QACnC,IAAI,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,WAAW,CAAC,EAAE,CAAC;YACpC,MAAM,YAAY,CAChB,iBAAiB,CAAC,mBAAmB,EACrC,mBAAmB,MAAM,CAAC,QAAQ,eAAe,SAAS,CAAC,WAAW,WAAW;gBAC/E,gFAAgF;gBAChF,wCAAwC,EAC1C;gBACE,QAAQ,EAAE,YAAY;gBACtB,OAAO,EAAE,EAAE,QAAQ,EAAE,MAAM,CAAC,QAAQ,EAAE,WAAW,EAAE,SAAS,CAAC,WAAW,EAAE;aAC3E,CACF,CAAC;QACJ,CAAC;QACD,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC;IAClC,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,UAAU,oBAAoB,CAAC,SAA2B;IAC9D,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC;QAC9B,WAAW,EAAE,SAAS,CAAC,WAAW;QAClC,IAAI,EAAE,SAAS,CAAC,IAAI;QACpB,WAAW,EAAE,SAAS,CAAC,WAAW;QAClC,WAAW,EAAE,CAAC,GAAG,SAAS,CAAC,WAAW,CAAC,CAAC,IAAI,EAAE;KAC/C,CAAC,CAAC;IACH,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AACrE,CAAC"}
1
+ {"version":3,"file":"bundle.js","sourceRoot":"","sources":["../src/bundle.ts"],"names":[],"mappings":"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"}
package/dist/errors.d.ts CHANGED
@@ -24,6 +24,13 @@ export declare const ReleaseErrorCodes: {
24
24
  readonly DUPLICATE_OPERATION: "ALDUS_RELEASE_DUPLICATE_OPERATION";
25
25
  /** A bundle was constructed with no operations at all. */
26
26
  readonly EMPTY_BUNDLE: "ALDUS_RELEASE_EMPTY_BUNDLE";
27
+ /**
28
+ * An operation declaration is not usable as written (#169).
29
+ *
30
+ * Today: a repeatable declaration with no reason. It licenses performing an external effect more
31
+ * than once, and an approver cannot accept that from a bare flag (§13.4, §17).
32
+ */
33
+ readonly OPERATION_INVALID: "ALDUS_RELEASE_OPERATION_INVALID";
27
34
  /**
28
35
  * A required operation failed, so the release did not complete.
29
36
  *
@@ -1 +1 @@
1
- {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,UAAU,EAAE,KAAK,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAErE,kDAAkD;AAClD,eAAO,MAAM,iBAAiB;IAC5B;;;;;;;OAOG;aACH,sBAAsB,EAAE,8BAA8B;IACtD,+DAA+D;aAC/D,sBAAsB,EAAE,sCAAsC;IAC9D,oEAAoE;aACpE,mBAAmB,EAAE,mCAAmC;IACxD,0DAA0D;aAC1D,YAAY,EAAE,4BAA4B;IAC1C;;;;;;OAMG;aACH,yBAAyB,EAAE,yCAAyC;IACpE;;;;;;OAMG;aACH,gBAAgB,EAAE,gCAAgC;IAClD;;;;;;OAMG;aACH,0BAA0B,EAAE,0CAA0C;CAC9D,CAAC;AAEX,6BAA6B;AAC7B,MAAM,MAAM,gBAAgB,GAAG,CAAC,OAAO,iBAAiB,CAAC,CAAC,MAAM,OAAO,iBAAiB,CAAC,CAAC;AAE1F,2DAA2D;AAC3D,wBAAgB,YAAY,CAC1B,IAAI,EAAE,gBAAgB,EACtB,OAAO,EAAE,MAAM,EACf,OAAO,EAAE;IAAE,QAAQ,EAAE,aAAa,CAAC;IAAC,SAAS,CAAC,EAAE,OAAO,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,GAC3F,UAAU,CAEZ"}
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,UAAU,EAAE,KAAK,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAErE,kDAAkD;AAClD,eAAO,MAAM,iBAAiB;IAC5B;;;;;;;OAOG;aACH,sBAAsB,EAAE,8BAA8B;IACtD,+DAA+D;aAC/D,sBAAsB,EAAE,sCAAsC;IAC9D,oEAAoE;aACpE,mBAAmB,EAAE,mCAAmC;IACxD,0DAA0D;aAC1D,YAAY,EAAE,4BAA4B;IAC1C;;;;;OAKG;aACH,iBAAiB,EAAE,iCAAiC;IACpD;;;;;;OAMG;aACH,yBAAyB,EAAE,yCAAyC;IACpE;;;;;;OAMG;aACH,gBAAgB,EAAE,gCAAgC;IAClD;;;;;;OAMG;aACH,0BAA0B,EAAE,0CAA0C;CAC9D,CAAC;AAEX,6BAA6B;AAC7B,MAAM,MAAM,gBAAgB,GAAG,CAAC,OAAO,iBAAiB,CAAC,CAAC,MAAM,OAAO,iBAAiB,CAAC,CAAC;AAE1F,2DAA2D;AAC3D,wBAAgB,YAAY,CAC1B,IAAI,EAAE,gBAAgB,EACtB,OAAO,EAAE,MAAM,EACf,OAAO,EAAE;IAAE,QAAQ,EAAE,aAAa,CAAC;IAAC,SAAS,CAAC,EAAE,OAAO,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,GAC3F,UAAU,CAEZ"}
package/dist/errors.js CHANGED
@@ -24,6 +24,13 @@ export const ReleaseErrorCodes = {
24
24
  DUPLICATE_OPERATION: "ALDUS_RELEASE_DUPLICATE_OPERATION",
25
25
  /** A bundle was constructed with no operations at all. */
26
26
  EMPTY_BUNDLE: "ALDUS_RELEASE_EMPTY_BUNDLE",
27
+ /**
28
+ * An operation declaration is not usable as written (#169).
29
+ *
30
+ * Today: a repeatable declaration with no reason. It licenses performing an external effect more
31
+ * than once, and an approver cannot accept that from a bare flag (§13.4, §17).
32
+ */
33
+ OPERATION_INVALID: "ALDUS_RELEASE_OPERATION_INVALID",
27
34
  /**
28
35
  * A required operation failed, so the release did not complete.
29
36
  *
@@ -1 +1 @@
1
- {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,UAAU,EAAsB,MAAM,qBAAqB,CAAC;AAErE,kDAAkD;AAClD,MAAM,CAAC,MAAM,iBAAiB,GAAG;IAC/B;;;;;;;OAOG;IACH,sBAAsB,EAAE,8BAA8B;IACtD,+DAA+D;IAC/D,sBAAsB,EAAE,sCAAsC;IAC9D,oEAAoE;IACpE,mBAAmB,EAAE,mCAAmC;IACxD,0DAA0D;IAC1D,YAAY,EAAE,4BAA4B;IAC1C;;;;;;OAMG;IACH,yBAAyB,EAAE,yCAAyC;IACpE;;;;;;OAMG;IACH,gBAAgB,EAAE,gCAAgC;IAClD;;;;;;OAMG;IACH,0BAA0B,EAAE,0CAA0C;CAC9D,CAAC;AAKX,2DAA2D;AAC3D,MAAM,UAAU,YAAY,CAC1B,IAAsB,EACtB,OAAe,EACf,OAA4F;IAE5F,OAAO,IAAI,UAAU,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;AAChD,CAAC"}
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,UAAU,EAAsB,MAAM,qBAAqB,CAAC;AAErE,kDAAkD;AAClD,MAAM,CAAC,MAAM,iBAAiB,GAAG;IAC/B;;;;;;;OAOG;IACH,sBAAsB,EAAE,8BAA8B;IACtD,+DAA+D;IAC/D,sBAAsB,EAAE,sCAAsC;IAC9D,oEAAoE;IACpE,mBAAmB,EAAE,mCAAmC;IACxD,0DAA0D;IAC1D,YAAY,EAAE,4BAA4B;IAC1C;;;;;OAKG;IACH,iBAAiB,EAAE,iCAAiC;IACpD;;;;;;OAMG;IACH,yBAAyB,EAAE,yCAAyC;IACpE;;;;;;OAMG;IACH,gBAAgB,EAAE,gCAAgC;IAClD;;;;;;OAMG;IACH,0BAA0B,EAAE,0CAA0C;CAC9D,CAAC;AAKX,2DAA2D;AAC3D,MAAM,UAAU,YAAY,CAC1B,IAAsB,EACtB,OAAe,EACf,OAA4F;IAE5F,OAAO,IAAI,UAAU,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;AAChD,CAAC"}
@@ -67,7 +67,6 @@ export interface BundleStatus {
67
67
  /** Operation ids that still need to run for the release to complete. */
68
68
  remaining: string[];
69
69
  }
70
- /** What reconciliation did about one operation. */
71
70
  export interface ReconciliationFinding {
72
71
  operationId: string;
73
72
  idempotencyKey: string;
@@ -79,7 +78,21 @@ export interface ReconciliationFinding {
79
78
  /** The destination does not hold it, so the operation genuinely still needs to run. */
80
79
  | "confirmed_absent"
81
80
  /** The adapter cannot query the destination (contract §17 "where the platform allows it"). */
82
- | "unavailable";
81
+ | "unavailable"
82
+ /**
83
+ * The operation declares its effect safe to repeat, so it was not queried (#169).
84
+ *
85
+ * Deliberately its own action rather than folded into `confirmed_absent`. Nothing was
86
+ * searched for, and saying it was absent would be the false statement this exists to stop.
87
+ */
88
+ | "not_reconciled_repeatable"
89
+ /**
90
+ * The adapter was asked and could not establish whether the operation happened (#169).
91
+ *
92
+ * Distinct from `unavailable`, which is a destination that cannot be queried at all, and from
93
+ * `confirmed_absent`, which is a completed search. This one asked and got no answer.
94
+ */
95
+ | "cannot_establish";
83
96
  explanation?: string;
84
97
  }
85
98
  /** What a reconciliation pass found. */
@@ -1 +1 @@
1
- {"version":3,"file":"executor.d.ts","sourceRoot":"","sources":["../src/executor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAc,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAGhF,OAAO,KAAK,EAAE,eAAe,EAAkC,MAAM,cAAc,CAAC;AACpF,OAAO,EAA2C,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AAE1F,OAAO,KAAK,EAAE,oBAAoB,EAAoB,MAAM,gBAAgB,CAAC;AAC7E,OAAO,EAAe,KAAK,gBAAgB,EAAE,KAAK,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAC1F,OAAO,EAAuB,KAAK,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAEjF,kCAAkC;AAClC,eAAO,MAAM,gBAAgB;AAC3B,oDAAoD;AACpD,aAAa;AACb,wFAAwF;AACxF,SAAS;AACT,oCAAoC;AACpC,WAAW;AACX,iDAAiD;AACjD,QAAQ;AACR,mEAAmE;AACnE,SAAS,CACD,CAAC;AAEX,4BAA4B;AAC5B,MAAM,MAAM,cAAc,GAAG,CAAC,OAAO,gBAAgB,CAAC,CAAC,MAAM,CAAC,CAAC;AAE/D,sDAAsD;AACtD,MAAM,WAAW,eAAe;IAC9B,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,kDAAkD;IAClD,WAAW,EAAE,oBAAoB,CAAC;IAClC,cAAc,EAAE,MAAM,CAAC;IACvB,KAAK,EAAE,cAAc,CAAC;IACtB,uDAAuD;IACvD,OAAO,CAAC,EAAE,cAAc,CAAC;CAC1B;AAED,oFAAoF;AACpF,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd;;;OAGG;IACH,KAAK,EAAE,aAAa,GAAG,aAAa,GAAG,SAAS,GAAG,QAAQ,GAAG,WAAW,CAAC;IAC1E,UAAU,EAAE,eAAe,EAAE,CAAC;IAC9B,wEAAwE;IACxE,SAAS,EAAE,MAAM,EAAE,CAAC;CACrB;AAED,mDAAmD;AACnD,MAAM,WAAW,qBAAqB;IACpC,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,MAAM,CAAC;IACvB,MAAM;IACJ,yDAAyD;IACvD,kBAAkB;IACpB,sFAAsF;OACpF,UAAU;IACZ,uFAAuF;OACrF,kBAAkB;IACpB,8FAA8F;OAC5F,aAAa,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,wCAAwC;AACxC,MAAM,WAAW,oBAAoB;IACnC,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,qBAAqB,EAAE,CAAC;IAClC,mDAAmD;IACnD,QAAQ,EAAE,cAAc,EAAE,CAAC;CAC5B;AAED,wCAAwC;AACxC,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd;;;OAGG;IACH,KAAK,EAAE,WAAW,GAAG,QAAQ,GAAG,SAAS,CAAC;IAC1C,MAAM,EAAE,YAAY,CAAC;IACrB,+CAA+C;IAC/C,OAAO,EAAE,cAAc,EAAE,CAAC;IAC1B,+FAA+F;IAC/F,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,gBAAgB;IAC/B,2CAA2C;IAC3C,KAAK,EAAE,QAAQ,CAAC;CACjB;AAED,iCAAiC;AACjC,MAAM,WAAW,cAAc;IAC7B,uFAAuF;IACvF,KAAK,EAAE,QAAQ,CAAC;IAChB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,4CAA4C;AAC5C,MAAM,WAAW,sBAAsB;IACrC,QAAQ,EAAE,eAAe,CAAC;IAC1B,QAAQ,EAAE,mBAAmB,CAAC;IAC9B,MAAM,EAAE,gBAAgB,CAAC;IACzB,kGAAkG;IAClG,UAAU,CAAC,EAAE,iBAAiB,CAAC;IAC/B,wCAAwC;IACxC,GAAG,CAAC,EAAE,MAAM,IAAI,CAAC;IACjB,wCAAwC;IACxC,aAAa,CAAC,EAAE,MAAM,MAAM,CAAC;IAC7B,wCAAwC;IACxC,WAAW,CAAC,EAAE,MAAM,MAAM,CAAC;CAC5B;AAED,2CAA2C;AAC3C,qBAAa,eAAe;;IAS1B,YAAY,OAAO,EAAE,sBAAsB,EAQ1C;IAED;;;;OAIG;IACG,MAAM,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,YAAY,CAAC,CAmBzD;IAED;;;;;;OAMG;IACG,SAAS,CAAC,MAAM,EAAE,aAAa,EAAE,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAoE/F;IAED;;;;OAIG;IACG,OAAO,CAAC,MAAM,EAAE,aAAa,EAAE,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,cAAc,CAAC,CAsHrF;CAwGF"}
1
+ {"version":3,"file":"executor.d.ts","sourceRoot":"","sources":["../src/executor.ts"],"names":[],"mappings":"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"}
package/dist/executor.js CHANGED
@@ -23,6 +23,7 @@
23
23
  * the work it describes did not.
24
24
  */
25
25
  import { SCHEMA_VERSION, newEventId, newReleaseId } from "@aldus-runtime/core";
26
+ import { cannotEstablish, isCannotEstablish } from "./adapter.js";
26
27
  import { assertBundleValid, deriveIdempotencyKey } from "./bundle.js";
27
28
  import { ReleaseErrorCodes, releaseError } from "./errors.js";
28
29
  import { latestByKey } from "./ports.js";
@@ -40,6 +41,37 @@ export const OPERATION_STATES = [
40
41
  /** Deliberately not attempted — no authority, or nothing to do. */
41
42
  "skipped",
42
43
  ];
44
+ /** What reconciliation did about one operation. */
45
+ /**
46
+ * Which reconciliation findings an operator is warned about (§20; #169).
47
+ *
48
+ * An **allow-list**, keyed on the action. It was a deny-list — any finding carrying an explanation
49
+ * except `confirmed_absent` — so every action added later was opted in by default, with nobody
50
+ * deciding. `not_reconciled_repeatable` was opted in exactly that way, and appeared in the
51
+ * warnings of every ordinary release of a bundle holding a repeatable operation, reporting that
52
+ * the expected thing had happened.
53
+ *
54
+ * Membership answers the question a warning asks: **did something go differently than intended?**
55
+ *
56
+ * - `repaired` — yes. The local record was wrong and had to be corrected.
57
+ * - `unavailable` — yes. Reconciliation could not run, so an operator is being told that a
58
+ * protection they may assume is absent.
59
+ * - `confirmed_absent` — no. The ordinary path; most operations in a fresh bundle are absent, and
60
+ * one line each would bury the two above.
61
+ * - `not_reconciled_repeatable` — no. The declared, approved state of the bundle. The reason is in
62
+ * the reconciliation report for anyone reading it.
63
+ *
64
+ * Adding an action now requires either adding it here or leaving it out, and both are decisions.
65
+ * Neither happens by default.
66
+ */
67
+ const OPERATOR_FACING_FINDINGS = new Set([
68
+ "repaired",
69
+ "unavailable",
70
+ // Yes. The operation may or may not have happened and nobody can tell — the state an operator
71
+ // most needs to see, and the one the deny-list would have surfaced by accident rather than by
72
+ // decision (#169).
73
+ "cannot_establish",
74
+ ]);
43
75
  /** Executes release bundles, resumably. */
44
76
  export class ReleaseExecutor {
45
77
  #adapters;
@@ -91,7 +123,7 @@ export class ReleaseExecutor {
91
123
  const latest = latestByKey(await this.#receipts.list(bundle.runId));
92
124
  const findings = [];
93
125
  const repaired = [];
94
- for (const { operation, idempotencyKey } of this.#plan(bundle)) {
126
+ for (const { operation, criticality, idempotencyKey } of this.#plan(bundle)) {
95
127
  const receipt = latest.get(idempotencyKey);
96
128
  if (receipt !== undefined && receipt.status !== "pending") {
97
129
  findings.push({
@@ -101,6 +133,21 @@ export class ReleaseExecutor {
101
133
  });
102
134
  continue;
103
135
  }
136
+ // Asked of nothing, because the answer would change nothing. A repeatable operation may be
137
+ // performed again whatever the destination holds, so querying it buys no decision — and
138
+ // where reconciliation runs before execution, as it does inside `execute`, a "not there"
139
+ // reading can mean "the operation before it has not run yet" rather than "it did not
140
+ // happen". An adapter forced to answer that question has to invent one (#169).
141
+ if (operation.repeatable !== undefined) {
142
+ findings.push({
143
+ operationId: operation.operationId,
144
+ idempotencyKey,
145
+ action: "not_reconciled_repeatable",
146
+ explanation: `"${operation.operationId}" declares its effect safe to repeat, so the destination ` +
147
+ `was not queried: ${operation.repeatable.reason}`,
148
+ });
149
+ continue;
150
+ }
104
151
  const adapter = this.#adapters.require(operation.destination);
105
152
  if (adapter.lookup === undefined) {
106
153
  findings.push({
@@ -117,8 +164,91 @@ export class ReleaseExecutor {
117
164
  idempotencyKey,
118
165
  runId: bundle.runId,
119
166
  };
120
- const remote = await adapter.lookup(request);
167
+ // Guarded, and the guard is criticality-aware. `reconcile` used to `await` this bare, so a
168
+ // throw from any operation's `lookup` aborted the whole pass before a single `execute` —
169
+ // a quota error raised while asking about a tidy-up blocked the upload beside it (#169).
170
+ //
171
+ // A required operation's throw still aborts. Fail-closed belongs where a mistake publishes
172
+ // twice; it does not belong where a best-effort tidy-up could not be queried.
173
+ let remote;
174
+ try {
175
+ remote = await adapter.lookup(request);
176
+ }
177
+ catch (thrown) {
178
+ if (criticality === "required")
179
+ throw thrown;
180
+ remote = cannotEstablish(`querying destination "${operation.destination}" failed: ${thrown instanceof Error ? thrown.message : String(thrown)}`);
181
+ }
182
+ if (isCannotEstablish(remote)) {
183
+ // Asked, and no answer. What happens next depends on what the operation is for.
184
+ if (criticality === "required") {
185
+ // Unknown must never license a publish. A required operation whose prior outcome cannot
186
+ // be established is refused before anything executes, because performing it might
187
+ // repeat it and skipping it might drop it, and neither is a choice to make blind.
188
+ throw releaseError(ReleaseErrorCodes.RECONCILIATION_UNAVAILABLE, `Whether required operation "${operation.operationId}" already happened could not be ` +
189
+ `established: ${remote.reason}. Executing would risk performing it twice and ` +
190
+ "skipping it would risk dropping it, so the bundle is refused until the outcome is " +
191
+ "known (contract §17).", {
192
+ category: "conflict",
193
+ retryable: false,
194
+ details: {
195
+ runId: bundle.runId,
196
+ operationId: operation.operationId,
197
+ destination: operation.destination,
198
+ },
199
+ });
200
+ }
201
+ // Best-effort: **no receipt**, and not performed this pass.
202
+ //
203
+ // Writing a `skipped` receipt was the obvious thing and it was wrong in the way this whole
204
+ // issue is about. `skipped` is terminal in both directions — `execute` skips it and
205
+ // `reconcile` treats it as already recorded — so one momentary query failure permanently
206
+ // retired the operation for that bundle. At the destination that produced #169 quota
207
+ // exhaustion is routine, so a rate limit while *asking about* a tidy-up would silently
208
+ // drop it forever, and the record would say `skipped`: a decision, when what happened was
209
+ // an unanswered question.
210
+ //
211
+ // A durable record of a transient failure is the same defect as a durable record of a
212
+ // search nobody performed. So the not-performing lives in this pass only — the finding and
213
+ // the warning say what happened now, and the next pass asks again.
214
+ //
215
+ // Not performed either, because executing on an unknown prior state is the other half of
216
+ // the trap: it might repeat an operation that already happened.
217
+ findings.push({
218
+ operationId: operation.operationId,
219
+ idempotencyKey,
220
+ action: "cannot_establish",
221
+ explanation: `Whether "${operation.operationId}" already happened could not be established, so it ` +
222
+ `was not attempted: ${remote.reason}`,
223
+ });
224
+ continue;
225
+ }
226
+ // Not an issued cannot-establish, so it must be the other arm — and that has to be checked
227
+ // rather than assumed. An assembled `{ reason: "..." }` is not an issued state, so
228
+ // `isCannotEstablish` correctly rejects it, and it would then reach `!remote.exists` with
229
+ // `exists` undefined and be recorded as `confirmed_absent`: a completed search, asserted
230
+ // from a value that is not a `RemoteState` at all.
231
+ //
232
+ // Fifth instance of the same lesson in this contract (#170 has the other four). A narrowing
233
+ // that trusts the declared type is not a narrowing.
234
+ if (typeof remote.exists !== "boolean") {
235
+ throw releaseError(ReleaseErrorCodes.OPERATION_INVALID, `The adapter for destination "${operation.destination}" answered the lookup for ` +
236
+ `"${operation.operationId}" with a value that is neither a remote state nor an ` +
237
+ "issued `cannotEstablish`. Reading it as absent would record a completed search that " +
238
+ "did not happen (contract §17).", {
239
+ category: "validation",
240
+ retryable: false,
241
+ details: {
242
+ runId: bundle.runId,
243
+ operationId: operation.operationId,
244
+ destination: operation.destination,
245
+ },
246
+ });
247
+ }
121
248
  if (!remote.exists) {
249
+ // Reserved to a `lookup` that actually returned `exists: false`. Every other reason an
250
+ // operation might not have been found now has its own action, so this one means what it
251
+ // says: a completed search established that the destination does not hold it (#169).
122
252
  findings.push({
123
253
  operationId: operation.operationId,
124
254
  idempotencyKey,
@@ -151,11 +281,18 @@ export class ReleaseExecutor {
151
281
  assertBundleValid(bundle);
152
282
  const warnings = [];
153
283
  const written = [];
284
+ // Operations this pass must not attempt, held in memory and never written down. A best-effort
285
+ // operation whose prior state could not be established is not performed now — executing on an
286
+ // unknown prior state might repeat something that already happened — and leaves no durable
287
+ // trace, so the next pass asks again rather than finding a terminal receipt (#169).
288
+ const unestablished = new Set();
154
289
  if (options.reconcile !== false) {
155
290
  const report = await this.reconcile(bundle, { actor: options.actor });
156
291
  written.push(...report.repaired);
157
292
  for (const finding of report.findings) {
158
- if (finding.explanation !== undefined && finding.action !== "confirmed_absent") {
293
+ if (finding.action === "cannot_establish")
294
+ unestablished.add(finding.operationId);
295
+ if (finding.explanation !== undefined && OPERATOR_FACING_FINDINGS.has(finding.action)) {
159
296
  warnings.push(finding.explanation);
160
297
  }
161
298
  }
@@ -172,13 +309,27 @@ export class ReleaseExecutor {
172
309
  "succeed.");
173
310
  continue;
174
311
  }
312
+ // Skipped for this pass only. No receipt was written, so a later pass reaches the same
313
+ // operation with the same undecided state and asks the destination again.
314
+ if (unestablished.has(operation.operationId))
315
+ continue;
175
316
  const existing = latest.get(idempotencyKey);
176
317
  if (existing?.status === "succeeded" || existing?.status === "skipped")
177
318
  continue;
178
319
  // An operation whose outcome was never confirmed must not be retried (contract §17). If
179
320
  // reconciliation could have resolved it, it already ran above and this receipt would be
180
321
  // terminal; reaching here means the destination cannot be queried.
181
- if (existing?.status === "pending") {
322
+ //
323
+ // Unless the operation says repeating it is safe, which is exactly the fact this refusal
324
+ // needs and previously had no way to hear. Without it a best-effort tidy-up whose outcome
325
+ // was once unconfirmed refused **every later release of that bundle** — the same operation
326
+ // whose failure `execute` treats as a warning, blocking the release forever because one
327
+ // past attempt went unanswered (#169).
328
+ if (existing?.status === "pending" && operation.repeatable !== undefined) {
329
+ warnings.push(`"${operation.operationId}" has an unconfirmed earlier outcome and declares its effect ` +
330
+ `safe to repeat, so it is being performed again: ${operation.repeatable.reason}`);
331
+ }
332
+ else if (existing?.status === "pending") {
182
333
  throw releaseError(ReleaseErrorCodes.RECONCILIATION_UNAVAILABLE, `"${operation.operationId}" was attempted and its outcome was never confirmed, and ` +
183
334
  `destination "${operation.destination}" cannot be queried to find out. Retrying ` +
184
335
  "would risk performing it twice, so it is refused until the outcome is established.", {
@@ -272,6 +423,11 @@ export class ReleaseExecutor {
272
423
  ...(outcome.status === "succeeded" && outcome.remoteUrl !== undefined
273
424
  ? { remoteUrl: outcome.remoteUrl }
274
425
  : {}),
426
+ // Carried through to the durable record. A field an adapter can set and nothing reads is
427
+ // the defect #107 reported, one contract over.
428
+ ...(outcome.status === "succeeded" && outcome.note !== undefined
429
+ ? { note: outcome.note }
430
+ : {}),
275
431
  // `pending` is not terminal, so it carries no completion time (contract §17).
276
432
  ...(outcome.status === "pending" ? {} : { completedAt: at }),
277
433
  ...(outcome.status === "failed"