@affiant/core 0.1.0-alpha.2 → 0.1.0-alpha.3
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 +121 -9
- package/dist/errors.d.ts +67 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +59 -0
- package/dist/errors.js.map +1 -1
- package/dist/gate/card.d.ts +98 -0
- package/dist/gate/card.d.ts.map +1 -0
- package/dist/gate/card.js +110 -0
- package/dist/gate/card.js.map +1 -0
- package/dist/gate/decide.js +13 -9
- package/dist/gate/decide.js.map +1 -1
- package/dist/gate/decision-result.d.ts +67 -0
- package/dist/gate/decision-result.d.ts.map +1 -0
- package/dist/gate/decision-result.js +68 -0
- package/dist/gate/decision-result.js.map +1 -0
- package/dist/gate/pipeline.d.ts +45 -0
- package/dist/gate/pipeline.d.ts.map +1 -1
- package/dist/gate/pipeline.js +66 -20
- package/dist/gate/pipeline.js.map +1 -1
- package/dist/index.d.ts +18 -10
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +15 -9
- package/dist/index.js.map +1 -1
- package/dist/model/amendments.d.ts +5 -3
- package/dist/model/amendments.d.ts.map +1 -1
- package/dist/model/amendments.js +7 -4
- package/dist/model/amendments.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -14,17 +14,16 @@ decision, and reports what happened; the gate never touches your database. Nothi
|
|
|
14
14
|
this depends on which model you use, which database you write to, or how the card
|
|
15
15
|
reaches the person — those are ports you supply.
|
|
16
16
|
|
|
17
|
-
> **On npm at `0.1.0-alpha.
|
|
18
|
-
> attestation, since 2026-09-
|
|
19
|
-
>
|
|
20
|
-
>
|
|
17
|
+
> **On npm at `0.1.0-alpha.3`**, under the `alpha` dist-tag and with a provenance
|
|
18
|
+
> attestation, since 2026-09-18: `npm i @affiant/core@alpha`. The publish workflow moves
|
|
19
|
+
> the `alpha` dist-tag and no other, so a bare `npm i @affiant/core` — which follows
|
|
20
|
+
> `latest` — does not reach this release. The condition for publishing was exact: a **public parity
|
|
21
21
|
> report** — the per-implementation list of conformance fixtures each implementation
|
|
22
22
|
> does not yet pass — and a **green, merge-blocking TypeScript conformance driver**
|
|
23
|
-
> running the shared fixture suite against this package. Both hold
|
|
24
|
-
> [`v0.
|
|
25
|
-
>
|
|
26
|
-
>
|
|
27
|
-
> [.NET parity report](https://github.com/Sakwala/affiant-protocol/blob/v0.1.3/conformance/parity/dotnet-v0.1.json)
|
|
23
|
+
> running the shared fixture suite against this package. Both hold, at the rulebook's
|
|
24
|
+
> [`v0.2.0`](https://github.com/Sakwala/affiant-protocol/releases/tag/v0.2.0) tag, which is what
|
|
25
|
+
> `packages/contract/protocol/PIN` pins and what both manifests are read at. The
|
|
26
|
+
> [.NET parity report](https://github.com/Sakwala/affiant-protocol/blob/v0.2.0/conformance/parity/dotnet-v0.1.json)
|
|
28
27
|
> is public, with its oracle run log alongside it under `conformance/results/`, and
|
|
29
28
|
> this package's own [conformance parity manifest](../conformance-driver/conformance/parity/typescript-v0.2.json)
|
|
30
29
|
> is green on Node, Bun and workerd, asserted by the `conformance` job that is
|
|
@@ -285,6 +284,119 @@ A decision that arrives late is refused as expired, and the amendments it carrie
|
|
|
285
284
|
so `gate.resubmit` files a fresh entry that prefills what the person had already typed
|
|
286
285
|
rather than making them type it twice (DK-1).
|
|
287
286
|
|
|
287
|
+
## The read side: a row a queue already holds
|
|
288
|
+
|
|
289
|
+
A filing hands you the Evidence Card for the entry it just filed. A review queue is the
|
|
290
|
+
other direction: it lists rows hours or days later, and all it has is the row. Two pure
|
|
291
|
+
producers build the envelopes from it — no store, no clock, no port, no network.
|
|
292
|
+
|
|
293
|
+
```ts
|
|
294
|
+
import { cardFor, decisionResultOf, isCallerError } from "@affiant/core";
|
|
295
|
+
import type { DocketEntry, EvidenceCardRequest } from "@affiant/core";
|
|
296
|
+
|
|
297
|
+
// A queue item: the row, the host's field schema for the tool that proposed it, and
|
|
298
|
+
// the instant you are rendering at.
|
|
299
|
+
const card: EvidenceCardRequest = cardFor(row, {
|
|
300
|
+
now: new Date().toISOString(),
|
|
301
|
+
schema, // the same FieldSchema the tool declares — optional
|
|
302
|
+
operationLabel: "Reprice", // your own verb for the operation — optional
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
if (card.requiresConfirmation) {
|
|
306
|
+
// Only then is a decision still being asked for.
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// After the decision: the report, for your own client or your audit surface.
|
|
310
|
+
try {
|
|
311
|
+
const result = decisionResultOf(row);
|
|
312
|
+
void result.outcome; // "approved" | "rejected" | "expired" | "resubmitted"
|
|
313
|
+
} catch (error) {
|
|
314
|
+
if (isCallerError(error) && error.kind === "entry-not-decided") {
|
|
315
|
+
// Nobody has decided this row yet, and this function reads no clock.
|
|
316
|
+
} else {
|
|
317
|
+
throw error;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// A row that supersedes another needs that row: the reviewer's earlier corrections
|
|
322
|
+
// live on it and nowhere else, so its absence is refused rather than read as `null`.
|
|
323
|
+
const superseded: DocketEntry | null =
|
|
324
|
+
row.lineage.supersedes === null ? null : await gate.get(row.lineage.supersedes, ctx);
|
|
325
|
+
const resubmissionCard = cardFor(row, {
|
|
326
|
+
now: new Date().toISOString(),
|
|
327
|
+
...(superseded === null ? {} : { superseded }),
|
|
328
|
+
});
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
What `cardFor` gives you, and what it does not:
|
|
332
|
+
|
|
333
|
+
- **`requiresConfirmation` is `true` only for a `pending`, unblocked row that has not
|
|
334
|
+
passed its deadline at `now`** — the same reading of the deadline the stores and the
|
|
335
|
+
sweep use (DK-1, DK-5). A blocked row's card says why and never claims a confirmation
|
|
336
|
+
is awaited (AZ-4). Every other row still has a card; only this flag says whether a
|
|
337
|
+
decision is being asked for.
|
|
338
|
+
- **No policy sentence.** A card built while filing carries the reason the policy chain
|
|
339
|
+
gave. The row records the chain's **verdict**, not its prose, so a card built from the
|
|
340
|
+
row carries the sentences the row itself determines — the blocked markers — and no
|
|
341
|
+
others.
|
|
342
|
+
- **`presentation` and `hostOperation` are whatever this call passes.** They are your
|
|
343
|
+
rendering of a proposal rather than its sworn substance, so they are not on the record
|
|
344
|
+
(SR-1). Widen a picker's `allowedValues` and every queue item renders the new set,
|
|
345
|
+
including rows filed before the change; the row names the tool that proposed it (CV-4),
|
|
346
|
+
which is how you find the declaration to pass.
|
|
347
|
+
- **`priorAmendments` comes from the Docket.** For a first filing it is what that row
|
|
348
|
+
preserved — the corrections a decision carried after the deadline had passed. For a row
|
|
349
|
+
that supersedes another it is the superseded row's, which you pass as `superseded`;
|
|
350
|
+
omitting it, passing the wrong row, passing one from another tenant, or passing one for
|
|
351
|
+
a row that supersedes nothing throws kind `superseded-entry-mismatch`.
|
|
352
|
+
- **The card of a row that changed since filing differs from the filing's card.** The
|
|
353
|
+
card shows the amended Affidavit and the numbers recomputed over it once an amendment
|
|
354
|
+
has been accepted (AF-2, AF-4), and a row whose deadline has since passed asks for no
|
|
355
|
+
confirmation. Only for the row **as it was filed** is the card the one the filing
|
|
356
|
+
returned.
|
|
357
|
+
|
|
358
|
+
What `decisionResultOf` gives you:
|
|
359
|
+
|
|
360
|
+
- The outcome from the status, except that an expired row reads **`resubmitted`** once a
|
|
361
|
+
successor has superseded it.
|
|
362
|
+
- **`attestation: null` and `execution: null` on anything but an approval.** The
|
|
363
|
+
envelope's `attestation` answers "who agreed", and a rejection and an expiry have no
|
|
364
|
+
answer (AZ-1) — even though the **row** of a rejection does name the person who
|
|
365
|
+
rejected it. The two documents answer different questions.
|
|
366
|
+
- A `pending` row throws kind `entry-not-decided`. Whether it has passed its deadline is
|
|
367
|
+
read against an instant, and this function is given none: settle it with the sweep, or
|
|
368
|
+
read the row's status at the instant you mean.
|
|
369
|
+
|
|
370
|
+
### Errors that are yours, not the gate's
|
|
371
|
+
|
|
372
|
+
A refusal is something the gate decided about a proposal, and it carries an `ErrorCode`
|
|
373
|
+
from the rulebook's registry. A mistake in your own code is not that. Four such mistakes
|
|
374
|
+
now throw `AffiantCallerError` — a subclass of `RangeError`, so anything catching one
|
|
375
|
+
today still catches it — with a stable `kind` and structured `details`:
|
|
376
|
+
|
|
377
|
+
| `kind` | When |
|
|
378
|
+
| --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
379
|
+
| `amendment-unknown-field` | An amendment names a field the Affidavit does not propose. It changes no state (DK-2), so you may catch it after `decide` rather than pre-check; the row stays decidable. |
|
|
380
|
+
| `turn-context-invalid` | The turn context's `conversationId`, `tenantId` or `channel` is blank. Thrown at the top of the pipeline, before the interceptors and before your model is called (GT-1): nothing is filed and no port runs. |
|
|
381
|
+
| `superseded-entry-mismatch` | `cardFor` was given the wrong superseded row, or none for a row that needs one, or one for a row that supersedes nothing. |
|
|
382
|
+
| `entry-not-decided` | `decisionResultOf` was given a `pending` row. |
|
|
383
|
+
|
|
384
|
+
- **A `kind` is not an `ErrorCode`.** It is not in the rulebook's refusal registry and it
|
|
385
|
+
never crosses the wire as one. `isCallerError(value)` is the guard, and it answers
|
|
386
|
+
truthfully even across two loaded copies of this package.
|
|
387
|
+
- **`kind` and `details` survive `JSON.stringify`; they do not survive
|
|
388
|
+
`structuredClone`.** They are own enumerable properties, so
|
|
389
|
+
`JSON.stringify(error)` reads `{"kind":…,"details":…,"name":"AffiantCallerError"}` —
|
|
390
|
+
enough to log or to send to your own client (`message` and `stack` are not enumerable
|
|
391
|
+
on any `Error`, so a JSON round trip is data and not an error, and `isCallerError`
|
|
392
|
+
reads `false` on it). `structuredClone` goes the other way: it carries an error's
|
|
393
|
+
`message`, `stack` and `cause` and drops every other own property, and the clone reads
|
|
394
|
+
`name: "Error"` — so a caller error does not cross a `postMessage` intact. Read `kind`
|
|
395
|
+
on the caught error, not on a copy of it.
|
|
396
|
+
- **A blank `turn.messageId` is not refused**, and neither is a blank utterance or an
|
|
397
|
+
absent `turn`. Only the three identifiers above are read at the top of the pipeline,
|
|
398
|
+
and the set of inputs the gate refuses did not change when they moved there.
|
|
399
|
+
|
|
288
400
|
## What this package does not claim
|
|
289
401
|
|
|
290
402
|
- **It swears to the field, not to the database.** An Affidavit is a record of what an
|
package/dist/errors.d.ts
CHANGED
|
@@ -150,4 +150,71 @@ export declare class AffiantError extends Error {
|
|
|
150
150
|
* from {@link ERROR_CODES}.
|
|
151
151
|
*/
|
|
152
152
|
export declare function isAffiantError(value: unknown): value is AffiantError;
|
|
153
|
+
/**
|
|
154
|
+
* Which programming mistake a caller made, from the closed set this release names.
|
|
155
|
+
*
|
|
156
|
+
* A kind is **not** an {@link ErrorCode}. It is not in the protocol's refusal
|
|
157
|
+
* registry, it never appears in {@link ERROR_CODES}, and it never crosses the wire
|
|
158
|
+
* as one: the registry names gate refusals only, and the rulebook classes an
|
|
159
|
+
* amendment naming a field the Affidavit does not propose, or a verdict naming a
|
|
160
|
+
* requirement outside the four, as a language-level error rather than a refusal
|
|
161
|
+
* code. These kinds live in this package, for a host that wants to tell one of its
|
|
162
|
+
* own mistakes from another without reading an error message.
|
|
163
|
+
*
|
|
164
|
+
* - `amendment-unknown-field` — an amendment named a field the Affidavit does not
|
|
165
|
+
* propose. The entry changes no state (DK-2).
|
|
166
|
+
* - `turn-context-invalid` — an identifier the turn context must carry was blank.
|
|
167
|
+
* - `superseded-entry-mismatch` — a card was asked for on a row that supersedes
|
|
168
|
+
* another, without the superseded row, or with the wrong one.
|
|
169
|
+
* - `entry-not-decided` — a decision report was asked for on a row still `pending`.
|
|
170
|
+
*/
|
|
171
|
+
export type CallerErrorKind = "amendment-unknown-field" | "turn-context-invalid" | "superseded-entry-mismatch" | "entry-not-decided";
|
|
172
|
+
/**
|
|
173
|
+
* Structured context attached to an {@link AffiantCallerError}: the field an
|
|
174
|
+
* amendment named, the entry it was made on, the identifier that was blank.
|
|
175
|
+
*
|
|
176
|
+
* Values are `unknown` because the useful details differ per kind, exactly as they
|
|
177
|
+
* do on {@link AffiantErrorDetails}. Never put a field value or an utterance in
|
|
178
|
+
* here — an error is not an audit record.
|
|
179
|
+
*/
|
|
180
|
+
export interface AffiantCallerErrorDetails {
|
|
181
|
+
readonly [key: string]: unknown;
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* A programming mistake in the calling code, told apart from the host's own bugs.
|
|
185
|
+
*
|
|
186
|
+
* This is **not a refusal**. A refusal is an {@link AffiantError} carrying an
|
|
187
|
+
* {@link ErrorCode} from the protocol's closed registry — something the gate decided
|
|
188
|
+
* about a proposal or a decision. This class is the other thing: an argument the
|
|
189
|
+
* caller could not legally have passed, which the rulebook calls a language-level
|
|
190
|
+
* error rather than a refusal code. It extends `RangeError`, so every host that
|
|
191
|
+
* already catches a `RangeError` from these call sites keeps working; what is new is
|
|
192
|
+
* that `kind` and `details` can be branched on instead of a message string.
|
|
193
|
+
*
|
|
194
|
+
* `kind` is not an `ErrorCode`, is not in the refusal registry, and is never sent as
|
|
195
|
+
* one. Use {@link isCallerError} rather than `instanceof` where two copies of this
|
|
196
|
+
* package may be loaded in one process.
|
|
197
|
+
*/
|
|
198
|
+
export declare class AffiantCallerError extends RangeError {
|
|
199
|
+
/** Which programming mistake was made. */
|
|
200
|
+
readonly kind: CallerErrorKind;
|
|
201
|
+
/** Structured context for the mistake. `{}` when the throwing site supplied none. */
|
|
202
|
+
readonly details: AffiantCallerErrorDetails;
|
|
203
|
+
/**
|
|
204
|
+
* @param kind Which programming mistake was made.
|
|
205
|
+
* @param message A human-readable explanation. Defaults to the kind itself.
|
|
206
|
+
* @param details Structured context for the mistake.
|
|
207
|
+
*/
|
|
208
|
+
constructor(kind: CallerErrorKind, message?: string, details?: AffiantCallerErrorDetails);
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Whether `value` is an {@link AffiantCallerError}.
|
|
212
|
+
*
|
|
213
|
+
* `instanceof` first, then a structural check, for the same reason
|
|
214
|
+
* {@link isAffiantError} has one: a host can end up with two copies of this package
|
|
215
|
+
* in one process, and a `catch` that spans that boundary still has to give a true
|
|
216
|
+
* answer. The structural arm is deliberately narrow — an `Error` named
|
|
217
|
+
* `AffiantCallerError` carrying one of the kinds above.
|
|
218
|
+
*/
|
|
219
|
+
export declare function isCallerError(value: unknown): value is AffiantCallerError;
|
|
153
220
|
//# sourceMappingURL=errors.d.ts.map
|
package/dist/errors.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,SAAS;IACpB;;;;;;;OAOG;;IAEH;;;;;;;OAOG;;IAEH;;;;OAIG;;IAEH;;;;OAIG;;IAEH,0EAA0E;;IAE1E;;;;OAIG;;IAEH;;;OAGG;;IAEH;;;;OAIG;;IAEH,kEAAkE;;IAElE;;;;;;;;;;;OAWG;;CAEK,CAAC;AAEX,+CAA+C;AAC/C,MAAM,MAAM,SAAS,GAAG,CAAC,OAAO,SAAS,CAAC,CAAC,MAAM,OAAO,SAAS,CAAC,CAAC;AAEnE;;;;;;;;GAQG;AACH,eAAO,MAAM,WAAW,iPAWiB,CAAC;AAE1C,gEAAgE;AAChE,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,SAAS,CAE9D;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,mBAAmB;IAClC,QAAQ,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACjC;AAED;;;;;;GAMG;AACH,qBAAa,YAAa,SAAQ,KAAK;IACrC,4BAA4B;IAC5B,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,qFAAqF;IACrF,QAAQ,CAAC,OAAO,EAAE,mBAAmB,CAAC;IAEtC;;;;OAIG;gBACS,IAAI,EAAE,SAAS,EAAE,OAAO,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,mBAAmB;CAM7E;AAED;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,YAAY,CAOpE"}
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,SAAS;IACpB;;;;;;;OAOG;;IAEH;;;;;;;OAOG;;IAEH;;;;OAIG;;IAEH;;;;OAIG;;IAEH,0EAA0E;;IAE1E;;;;OAIG;;IAEH;;;OAGG;;IAEH;;;;OAIG;;IAEH,kEAAkE;;IAElE;;;;;;;;;;;OAWG;;CAEK,CAAC;AAEX,+CAA+C;AAC/C,MAAM,MAAM,SAAS,GAAG,CAAC,OAAO,SAAS,CAAC,CAAC,MAAM,OAAO,SAAS,CAAC,CAAC;AAEnE;;;;;;;;GAQG;AACH,eAAO,MAAM,WAAW,iPAWiB,CAAC;AAE1C,gEAAgE;AAChE,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,SAAS,CAE9D;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,mBAAmB;IAClC,QAAQ,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACjC;AAED;;;;;;GAMG;AACH,qBAAa,YAAa,SAAQ,KAAK;IACrC,4BAA4B;IAC5B,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,qFAAqF;IACrF,QAAQ,CAAC,OAAO,EAAE,mBAAmB,CAAC;IAEtC;;;;OAIG;gBACS,IAAI,EAAE,SAAS,EAAE,OAAO,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,mBAAmB;CAM7E;AAED;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,YAAY,CAOpE;AAMD;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,MAAM,eAAe,GACvB,yBAAyB,GACzB,sBAAsB,GACtB,2BAA2B,GAC3B,mBAAmB,CAAC;AAexB;;;;;;;GAOG;AACH,MAAM,WAAW,yBAAyB;IACxC,QAAQ,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACjC;AAED;;;;;;;;;;;;;;GAcG;AACH,qBAAa,kBAAmB,SAAQ,UAAU;IAChD,0CAA0C;IAC1C,QAAQ,CAAC,IAAI,EAAE,eAAe,CAAC;IAC/B,qFAAqF;IACrF,QAAQ,CAAC,OAAO,EAAE,yBAAyB,CAAC;IAE5C;;;;OAIG;gBACS,IAAI,EAAE,eAAe,EAAE,OAAO,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,yBAAyB;CAMzF;AAED;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,kBAAkB,CAOzE"}
|
package/dist/errors.js
CHANGED
|
@@ -161,4 +161,63 @@ export function isAffiantError(value) {
|
|
|
161
161
|
value.name === "AffiantError" &&
|
|
162
162
|
isErrorCode(value.code));
|
|
163
163
|
}
|
|
164
|
+
/** Every {@link CallerErrorKind}, as data the guard below can test against. */
|
|
165
|
+
const CALLER_ERROR_KINDS = [
|
|
166
|
+
"amendment-unknown-field",
|
|
167
|
+
"turn-context-invalid",
|
|
168
|
+
"superseded-entry-mismatch",
|
|
169
|
+
"entry-not-decided",
|
|
170
|
+
];
|
|
171
|
+
/** Whether `value` is one of the kinds in {@link CallerErrorKind}. */
|
|
172
|
+
function isCallerErrorKind(value) {
|
|
173
|
+
return typeof value === "string" && CALLER_ERROR_KINDS.includes(value);
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* A programming mistake in the calling code, told apart from the host's own bugs.
|
|
177
|
+
*
|
|
178
|
+
* This is **not a refusal**. A refusal is an {@link AffiantError} carrying an
|
|
179
|
+
* {@link ErrorCode} from the protocol's closed registry — something the gate decided
|
|
180
|
+
* about a proposal or a decision. This class is the other thing: an argument the
|
|
181
|
+
* caller could not legally have passed, which the rulebook calls a language-level
|
|
182
|
+
* error rather than a refusal code. It extends `RangeError`, so every host that
|
|
183
|
+
* already catches a `RangeError` from these call sites keeps working; what is new is
|
|
184
|
+
* that `kind` and `details` can be branched on instead of a message string.
|
|
185
|
+
*
|
|
186
|
+
* `kind` is not an `ErrorCode`, is not in the refusal registry, and is never sent as
|
|
187
|
+
* one. Use {@link isCallerError} rather than `instanceof` where two copies of this
|
|
188
|
+
* package may be loaded in one process.
|
|
189
|
+
*/
|
|
190
|
+
export class AffiantCallerError extends RangeError {
|
|
191
|
+
/** Which programming mistake was made. */
|
|
192
|
+
kind;
|
|
193
|
+
/** Structured context for the mistake. `{}` when the throwing site supplied none. */
|
|
194
|
+
details;
|
|
195
|
+
/**
|
|
196
|
+
* @param kind Which programming mistake was made.
|
|
197
|
+
* @param message A human-readable explanation. Defaults to the kind itself.
|
|
198
|
+
* @param details Structured context for the mistake.
|
|
199
|
+
*/
|
|
200
|
+
constructor(kind, message, details) {
|
|
201
|
+
super(message ?? kind);
|
|
202
|
+
this.name = "AffiantCallerError";
|
|
203
|
+
this.kind = kind;
|
|
204
|
+
this.details = details ?? {};
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Whether `value` is an {@link AffiantCallerError}.
|
|
209
|
+
*
|
|
210
|
+
* `instanceof` first, then a structural check, for the same reason
|
|
211
|
+
* {@link isAffiantError} has one: a host can end up with two copies of this package
|
|
212
|
+
* in one process, and a `catch` that spans that boundary still has to give a true
|
|
213
|
+
* answer. The structural arm is deliberately narrow — an `Error` named
|
|
214
|
+
* `AffiantCallerError` carrying one of the kinds above.
|
|
215
|
+
*/
|
|
216
|
+
export function isCallerError(value) {
|
|
217
|
+
if (value instanceof AffiantCallerError)
|
|
218
|
+
return true;
|
|
219
|
+
return (value instanceof Error &&
|
|
220
|
+
value.name === "AffiantCallerError" &&
|
|
221
|
+
isCallerErrorKind(value.kind));
|
|
222
|
+
}
|
|
164
223
|
//# sourceMappingURL=errors.js.map
|
package/dist/errors.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG;IACvB;;;;;;;OAOG;IACH,6BAA6B,EAAE,6BAA6B;IAC5D;;;;;;;OAOG;IACH,kBAAkB,EAAE,kBAAkB;IACtC;;;;OAIG;IACH,mBAAmB,EAAE,mBAAmB;IACxC;;;;OAIG;IACH,uBAAuB,EAAE,uBAAuB;IAChD,0EAA0E;IAC1E,sBAAsB,EAAE,sBAAsB;IAC9C;;;;OAIG;IACH,kBAAkB,EAAE,kBAAkB;IACtC;;;OAGG;IACH,oBAAoB,EAAE,oBAAoB;IAC1C;;;;OAIG;IACH,gBAAgB,EAAE,gBAAgB;IAClC,kEAAkE;IAClE,iBAAiB,EAAE,iBAAiB;IACpC;;;;;;;;;;;OAWG;IACH,4BAA4B,EAAE,4BAA4B;CAClD,CAAC;AAKX;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG;IACzB,6BAA6B;IAC7B,kBAAkB;IAClB,mBAAmB;IACnB,uBAAuB;IACvB,sBAAsB;IACtB,kBAAkB;IAClB,oBAAoB;IACpB,gBAAgB;IAChB,iBAAiB;IACjB,4BAA4B;CACW,CAAC;AAE1C,gEAAgE;AAChE,MAAM,UAAU,WAAW,CAAC,KAAc;IACxC,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAK,WAAiC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;AACzF,CAAC;AAcD;;;;;;GAMG;AACH,MAAM,OAAO,YAAa,SAAQ,KAAK;IACrC,4BAA4B;IACnB,IAAI,CAAY;IACzB,qFAAqF;IAC5E,OAAO,CAAsB;IAEtC;;;;OAIG;IACH,YAAY,IAAe,EAAE,OAAgB,EAAE,OAA6B;QAC1E,KAAK,CAAC,OAAO,IAAI,IAAI,CAAC,CAAC;QACvB,IAAI,CAAC,IAAI,GAAG,cAAc,CAAC;QAC3B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,OAAO,GAAG,OAAO,IAAI,EAAE,CAAC;IAC/B,CAAC;CACF;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,cAAc,CAAC,KAAc;IAC3C,IAAI,KAAK,YAAY,YAAY;QAAE,OAAO,IAAI,CAAC;IAC/C,OAAO,CACL,KAAK,YAAY,KAAK;QACtB,KAAK,CAAC,IAAI,KAAK,cAAc;QAC7B,WAAW,CAAE,KAAqC,CAAC,IAAI,CAAC,CACzD,CAAC;AACJ,CAAC"}
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Evidence Card for an entry that is already on the Docket.
|
|
3
|
+
*
|
|
4
|
+
* **Rules served: SR-1** (a host's rendering of a proposal lives on the card
|
|
5
|
+
* envelope and not on the sworn record), **DK-1 and DK-5** (a row that reads expired
|
|
6
|
+
* is never presented as one still awaiting a decision), **AZ-4 and CV-4** (a blocked
|
|
7
|
+
* entry's card says so and never claims a confirmation is being awaited), **AF-2 and
|
|
8
|
+
* AF-4** (the card shows the amended record and the numbers recomputed over it).
|
|
9
|
+
*
|
|
10
|
+
* A filing already returns a card, but a review queue lists entries long after they
|
|
11
|
+
* were filed and has only the row. {@link cardFor} is that producer: pure, reading
|
|
12
|
+
* no store, no clock and no port, and building through the same internal builder the
|
|
13
|
+
* filing path uses so the two cannot drift.
|
|
14
|
+
*
|
|
15
|
+
* Two things the row cannot supply, and which therefore come from the caller: the
|
|
16
|
+
* per-field rendering hints and the host's own verb for the operation. SR-1 puts
|
|
17
|
+
* both on the envelope rather than on the record, and the Docket entry schema is
|
|
18
|
+
* closed and has nowhere to keep them — so they are whatever the host passes on
|
|
19
|
+
* *this* call, not what it passed at filing. The host can look them up: the row
|
|
20
|
+
* records the tool that proposed it (CV-4), and the host holds that tool's
|
|
21
|
+
* declaration.
|
|
22
|
+
*
|
|
23
|
+
* One thing a card built here does not carry: the sentence a policy chain gave at
|
|
24
|
+
* filing. The row records the policy's verdict, not its prose, and re-running the
|
|
25
|
+
* chain now would evaluate a different moment.
|
|
26
|
+
*
|
|
27
|
+
* @packageDocumentation
|
|
28
|
+
*/
|
|
29
|
+
import type { DocketEntry } from "../docket/entry.js";
|
|
30
|
+
import type { FieldSchema } from "../ports.js";
|
|
31
|
+
import type { EvidenceCardRequest } from "./pipeline.js";
|
|
32
|
+
/**
|
|
33
|
+
* What a card needs beside the row.
|
|
34
|
+
*
|
|
35
|
+
* Everything here except `now` is optional, and a card built with none of them is a
|
|
36
|
+
* complete card: it simply carries no rendering hints, no host verb, and — for a
|
|
37
|
+
* first filing — whatever the row itself preserved.
|
|
38
|
+
*/
|
|
39
|
+
export interface CardForOptions {
|
|
40
|
+
/**
|
|
41
|
+
* The instant to read the row's deadline against, in any form
|
|
42
|
+
* {@link readStatus} accepts — it measures the deadline with `instantMs`, which
|
|
43
|
+
* reads an instant with `Date.parse`, so an offset form is accepted as well as a
|
|
44
|
+
* UTC one. An instant that reader cannot read throws a plain `RangeError`.
|
|
45
|
+
*
|
|
46
|
+
* Required, and a parameter rather than a reading, because this package owns no
|
|
47
|
+
* clock: a card built from a row is built at a moment the caller knows and
|
|
48
|
+
* the function cannot. It is what decides whether a `pending` row is still
|
|
49
|
+
* awaiting a decision (DK-1).
|
|
50
|
+
*/
|
|
51
|
+
readonly now: string;
|
|
52
|
+
/**
|
|
53
|
+
* The host's field schema for the operation that proposed this row — the source of
|
|
54
|
+
* the card's per-field rendering hints.
|
|
55
|
+
*
|
|
56
|
+
* Absent, the card carries no `presentation`. The hints are the host's rendering
|
|
57
|
+
* of the proposal and not its sworn substance (SR-1), so they are supplied here
|
|
58
|
+
* rather than read off the row: a host that later widens a picker's set serves the
|
|
59
|
+
* widened set for every entry, including ones filed before the change.
|
|
60
|
+
*/
|
|
61
|
+
readonly schema?: FieldSchema;
|
|
62
|
+
/**
|
|
63
|
+
* The host's own verb for the operation — `"Onboard"`, `"Reprice"`.
|
|
64
|
+
*
|
|
65
|
+
* Absent, the card omits `hostOperation`, which the envelope allows.
|
|
66
|
+
*/
|
|
67
|
+
readonly operationLabel?: string;
|
|
68
|
+
/**
|
|
69
|
+
* The entry this one supersedes, as `gate.get` returns it.
|
|
70
|
+
*
|
|
71
|
+
* **Required when the row supersedes another**, because a reviewer's earlier
|
|
72
|
+
* corrections live on that row and nowhere else, and a card that silently dropped
|
|
73
|
+
* them would still be a valid envelope and still be wrong. Passing it for a row
|
|
74
|
+
* that supersedes nothing, or passing the wrong row, is a caller error.
|
|
75
|
+
*/
|
|
76
|
+
readonly superseded?: DocketEntry;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* The Evidence Card for `entry`, as a reviewer surface should see it at `now`.
|
|
80
|
+
*
|
|
81
|
+
* Pure: no store, no clock, no port, no network. The same builder the filing path
|
|
82
|
+
* uses, so for the same row, the same `schema`, the same `operationLabel`, the same
|
|
83
|
+
* superseded row and an instant before the deadline, this returns the card the
|
|
84
|
+
* filing returned — except that it carries no policy sentence in `warnings`.
|
|
85
|
+
*
|
|
86
|
+
* Works for a row in any status. An approved row, a rejected row and an expired row
|
|
87
|
+
* all have cards; `requiresConfirmation` is the one field that says whether a
|
|
88
|
+
* decision is being asked for, and it is `true` only for a row that is `pending`, is
|
|
89
|
+
* not blocked, and has not passed its deadline at `now` (DK-1, DK-5, AZ-4).
|
|
90
|
+
*
|
|
91
|
+
* @throws AffiantCallerError of kind `superseded-entry-mismatch` when the row
|
|
92
|
+
* supersedes another and `options.superseded` is missing, is a different
|
|
93
|
+
* entry, or belongs to another tenant — or when it is supplied for a row
|
|
94
|
+
* that supersedes nothing.
|
|
95
|
+
* @throws RangeError when `now` or the row's `expiresAt` is not a readable instant.
|
|
96
|
+
*/
|
|
97
|
+
export declare function cardFor(entry: DocketEntry, options: CardForOptions): EvidenceCardRequest;
|
|
98
|
+
//# sourceMappingURL=card.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"card.d.ts","sourceRoot":"","sources":["../../src/gate/card.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAGtD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE/C,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AAGzD;;;;;;GAMG;AACH,MAAM,WAAW,cAAc;IAC7B;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB;;;;;;;;OAQG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC;IAC9B;;;;OAIG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IACjC;;;;;;;OAOG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,WAAW,CAAC;CACnC;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,OAAO,CAAC,KAAK,EAAE,WAAW,EAAE,OAAO,EAAE,cAAc,GAAG,mBAAmB,CAexF"}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Evidence Card for an entry that is already on the Docket.
|
|
3
|
+
*
|
|
4
|
+
* **Rules served: SR-1** (a host's rendering of a proposal lives on the card
|
|
5
|
+
* envelope and not on the sworn record), **DK-1 and DK-5** (a row that reads expired
|
|
6
|
+
* is never presented as one still awaiting a decision), **AZ-4 and CV-4** (a blocked
|
|
7
|
+
* entry's card says so and never claims a confirmation is being awaited), **AF-2 and
|
|
8
|
+
* AF-4** (the card shows the amended record and the numbers recomputed over it).
|
|
9
|
+
*
|
|
10
|
+
* A filing already returns a card, but a review queue lists entries long after they
|
|
11
|
+
* were filed and has only the row. {@link cardFor} is that producer: pure, reading
|
|
12
|
+
* no store, no clock and no port, and building through the same internal builder the
|
|
13
|
+
* filing path uses so the two cannot drift.
|
|
14
|
+
*
|
|
15
|
+
* Two things the row cannot supply, and which therefore come from the caller: the
|
|
16
|
+
* per-field rendering hints and the host's own verb for the operation. SR-1 puts
|
|
17
|
+
* both on the envelope rather than on the record, and the Docket entry schema is
|
|
18
|
+
* closed and has nowhere to keep them — so they are whatever the host passes on
|
|
19
|
+
* *this* call, not what it passed at filing. The host can look them up: the row
|
|
20
|
+
* records the tool that proposed it (CV-4), and the host holds that tool's
|
|
21
|
+
* declaration.
|
|
22
|
+
*
|
|
23
|
+
* One thing a card built here does not carry: the sentence a policy chain gave at
|
|
24
|
+
* filing. The row records the policy's verdict, not its prose, and re-running the
|
|
25
|
+
* chain now would evaluate a different moment.
|
|
26
|
+
*
|
|
27
|
+
* @packageDocumentation
|
|
28
|
+
*/
|
|
29
|
+
import { readStatus } from "../docket/entry.js";
|
|
30
|
+
import { AffiantCallerError } from "../errors.js";
|
|
31
|
+
import { buildCard } from "./pipeline.js";
|
|
32
|
+
/**
|
|
33
|
+
* The Evidence Card for `entry`, as a reviewer surface should see it at `now`.
|
|
34
|
+
*
|
|
35
|
+
* Pure: no store, no clock, no port, no network. The same builder the filing path
|
|
36
|
+
* uses, so for the same row, the same `schema`, the same `operationLabel`, the same
|
|
37
|
+
* superseded row and an instant before the deadline, this returns the card the
|
|
38
|
+
* filing returned — except that it carries no policy sentence in `warnings`.
|
|
39
|
+
*
|
|
40
|
+
* Works for a row in any status. An approved row, a rejected row and an expired row
|
|
41
|
+
* all have cards; `requiresConfirmation` is the one field that says whether a
|
|
42
|
+
* decision is being asked for, and it is `true` only for a row that is `pending`, is
|
|
43
|
+
* not blocked, and has not passed its deadline at `now` (DK-1, DK-5, AZ-4).
|
|
44
|
+
*
|
|
45
|
+
* @throws AffiantCallerError of kind `superseded-entry-mismatch` when the row
|
|
46
|
+
* supersedes another and `options.superseded` is missing, is a different
|
|
47
|
+
* entry, or belongs to another tenant — or when it is supplied for a row
|
|
48
|
+
* that supersedes nothing.
|
|
49
|
+
* @throws RangeError when `now` or the row's `expiresAt` is not a readable instant.
|
|
50
|
+
*/
|
|
51
|
+
export function cardFor(entry, options) {
|
|
52
|
+
return buildCard(entry, {
|
|
53
|
+
priorAmendments: priorAmendmentsFor(entry, options.superseded),
|
|
54
|
+
schema: options.schema ?? null,
|
|
55
|
+
operationLabel: options.operationLabel ?? null,
|
|
56
|
+
// The row records the policy's verdict, not the sentence it gave (SR-1 keeps
|
|
57
|
+
// prose off the record), and re-running the chain here would judge a different
|
|
58
|
+
// moment against a host risk function this function is not allowed to call.
|
|
59
|
+
policyReason: null,
|
|
60
|
+
// DK-5: a row that reads expired is never presented as pending, swept or not —
|
|
61
|
+
// so the deadline is measured with the reading the stores and the sweep use
|
|
62
|
+
// rather than with a second comparison written here. AZ-4: a blocked entry's
|
|
63
|
+
// card never claims a confirmation is being awaited.
|
|
64
|
+
requiresConfirmation: readStatus(entry, options.now) === "pending" && entry.blocked === null,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* The amendments a reviewer already made, which the Docket holds on the row this
|
|
69
|
+
* entry replaced.
|
|
70
|
+
*
|
|
71
|
+
* A first filing answers from its own preserved map — the amendments a decision
|
|
72
|
+
* carried after the deadline had passed (DK-1). A resubmission answers from the row
|
|
73
|
+
* it supersedes, which is where the corrections that prefilled it were preserved,
|
|
74
|
+
* and which the caller must therefore hand over. The absence fails loudly rather
|
|
75
|
+
* than reading `null`: a card that quietly dropped a reviewer's earlier corrections
|
|
76
|
+
* validates against the envelope schema and is still wrong.
|
|
77
|
+
*/
|
|
78
|
+
function priorAmendmentsFor(entry, superseded) {
|
|
79
|
+
const supersedes = entry.lineage.supersedes;
|
|
80
|
+
if (supersedes === null) {
|
|
81
|
+
if (superseded !== undefined) {
|
|
82
|
+
throw new AffiantCallerError("superseded-entry-mismatch", `Docket entry ${JSON.stringify(entry.entryId)} supersedes no entry, so there is no ` +
|
|
83
|
+
`superseded row for its card to read prior amendments from; entry ` +
|
|
84
|
+
`${JSON.stringify(superseded.entryId)} was supplied`, { entryId: entry.entryId, supersedes: null, supplied: superseded.entryId });
|
|
85
|
+
}
|
|
86
|
+
return entry.preservedAmendments?.amendments ?? null;
|
|
87
|
+
}
|
|
88
|
+
if (superseded === undefined) {
|
|
89
|
+
throw new AffiantCallerError("superseded-entry-mismatch", `Docket entry ${JSON.stringify(entry.entryId)} supersedes entry ` +
|
|
90
|
+
`${JSON.stringify(supersedes)}, whose preserved amendments are the card's ` +
|
|
91
|
+
`priorAmendments; pass that entry as options.superseded`, { entryId: entry.entryId, supersedes, supplied: null });
|
|
92
|
+
}
|
|
93
|
+
if (superseded.entryId !== supersedes) {
|
|
94
|
+
throw new AffiantCallerError("superseded-entry-mismatch", `Docket entry ${JSON.stringify(entry.entryId)} supersedes entry ` +
|
|
95
|
+
`${JSON.stringify(supersedes)}, but options.superseded is entry ` +
|
|
96
|
+
`${JSON.stringify(superseded.entryId)}`, { entryId: entry.entryId, supersedes, supplied: superseded.entryId });
|
|
97
|
+
}
|
|
98
|
+
if (superseded.tenantId !== entry.tenantId) {
|
|
99
|
+
throw new AffiantCallerError("superseded-entry-mismatch", `Docket entry ${JSON.stringify(entry.entryId)} belongs to tenant ` +
|
|
100
|
+
`${JSON.stringify(entry.tenantId)} and options.superseded to tenant ` +
|
|
101
|
+
`${JSON.stringify(superseded.tenantId)}; a card reads no row from another tenant`, {
|
|
102
|
+
entryId: entry.entryId,
|
|
103
|
+
supersedes,
|
|
104
|
+
tenantId: entry.tenantId,
|
|
105
|
+
suppliedTenantId: superseded.tenantId,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
return superseded.preservedAmendments?.amendments ?? null;
|
|
109
|
+
}
|
|
110
|
+
//# sourceMappingURL=card.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"card.js","sourceRoot":"","sources":["../../src/gate/card.ts"],"names":[],"mappings":"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"}
|
package/dist/gate/decide.js
CHANGED
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
* @packageDocumentation
|
|
57
57
|
*/
|
|
58
58
|
import { readStatus } from "../docket/entry.js";
|
|
59
|
-
import { AffiantError } from "../errors.js";
|
|
59
|
+
import { AffiantCallerError, AffiantError } from "../errors.js";
|
|
60
60
|
import { applyAmendments, resolveAmendments } from "../model/amendments.js";
|
|
61
61
|
import { mintTag, supersede } from "../model/provenance.js";
|
|
62
62
|
import { runPipeline } from "./pipeline.js";
|
|
@@ -517,7 +517,7 @@ function refuseUnauthorized(entryId, ctx, path, reason, message, deps, now) {
|
|
|
517
517
|
async function refuseExpired(entryId, scope, entry, amendments, principal, now, deps) {
|
|
518
518
|
let preserved = false;
|
|
519
519
|
if (amendments !== null && Object.keys(amendments).length > 0) {
|
|
520
|
-
requireAmendableFields(entry.affidavit, amendments);
|
|
520
|
+
requireAmendableFields(entry.affidavit, amendments, entryId);
|
|
521
521
|
const attestor = attestorOf(principal);
|
|
522
522
|
if (attestor !== null) {
|
|
523
523
|
// The refused decision's **own** instant and principal (DK-1, PV-2): a
|
|
@@ -539,16 +539,20 @@ async function refuseExpired(entryId, scope, entry, amendments, principal, now,
|
|
|
539
539
|
/**
|
|
540
540
|
* Refuse an amendment map that names a field the Affidavit does not propose (DK-2).
|
|
541
541
|
*
|
|
542
|
-
*
|
|
543
|
-
* raises: the {@link ErrorCode} registry names refusals the gate
|
|
544
|
-
* proposal's substance or a decider's identity, and a field name that
|
|
545
|
-
* a caller passing an index out of range.
|
|
542
|
+
* An {@link AffiantCallerError} — still a `RangeError`, and still the message
|
|
543
|
+
* `applyAmendments` raises: the {@link ErrorCode} registry names refusals the gate
|
|
544
|
+
* makes about a proposal's substance or a decider's identity, and a field name that
|
|
545
|
+
* is not there is a caller passing an index out of range. The `kind` is what lets a
|
|
546
|
+
* host answer its own client about it without reading the message.
|
|
547
|
+
*
|
|
548
|
+
* DK-2: nothing is written before this runs, so the row is untouched and a later
|
|
549
|
+
* valid decision on it still succeeds.
|
|
546
550
|
*/
|
|
547
|
-
function requireAmendableFields(affidavit, map) {
|
|
551
|
+
function requireAmendableFields(affidavit, map, entryId) {
|
|
548
552
|
for (const resolved of resolveAmendments(map)) {
|
|
549
553
|
if (!affidavit.fields.some((field) => field.name === resolved.name)) {
|
|
550
|
-
throw new
|
|
551
|
-
`not propose
|
|
554
|
+
throw new AffiantCallerError("amendment-unknown-field", `amendment names field ${JSON.stringify(resolved.name)}, which this Affidavit does ` +
|
|
555
|
+
`not propose`, { field: resolved.name, entryId });
|
|
552
556
|
}
|
|
553
557
|
}
|
|
554
558
|
}
|