linked-rolls 0.74.0 → 0.75.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -132,6 +132,24 @@ edits the editor wrote, which hold their versions to what they insert
132
132
  and delete. Motivations, beliefs and versions known only from a
133
133
  recording remain the editor's to state.
134
134
 
135
+ What the program concluded is recorded as a program's conclusion. A
136
+ search scores whole stemmata, so the derivations and edits it states
137
+ are one proposition set (CRMinf I4) held under one belief. Each of
138
+ them names the set in its annotation, and the set states the belief
139
+ once. The belief, likely unless the editor says otherwise, rests on an
140
+ inference that names this library and its release, the copies it
141
+ collated, the published rules its costs carry out (`stemmaRules`) and,
142
+ in its note, the costs and settings it ran with and the margin of each
143
+ grouping. Who ran it and when are the editor's to give. A derivation or
144
+ an edit the editor holds for reasons of their own stays theirs, outside
145
+ the set.
146
+
147
+ ```ts
148
+ const run = { actor: editor, date: { within: new Date() } }
149
+ const adopted = produce(edition, adoptStemma(proposal, run))
150
+ adopted.propositionSets // [{ type: 'propositionSet', id, belief: { certainty: 'likely', reasons: [{ type: 'inference', software: [linkedRolls], … }] } }]
151
+ ```
152
+
135
153
  The costs are those with which the stemma of WM 225 comes out of its
136
154
  six copies. They were found by searching for such values, so they show
137
155
  that the stemma is consistent with these principles, not that the
@@ -151,7 +169,11 @@ edit, a note struck again or two tied; a held command standing for a
151
169
  latched pair is one recoding; a command the text makes no use of is a
152
170
  redundancy added or removed; cancels punched across lines at one place
153
171
  are one row. `adoptEdits` puts the proposal in place of the edits a
154
- collation wrote and nobody has motivated, and keeps the rest.
172
+ collation or a program's run wrote and nobody has motivated, and keeps
173
+ the rest. The edits it proposes are a proposition set of their own. The
174
+ inference behind its belief names the beliefs the derivations of the
175
+ versions are held with as its premises, so that edits grouped against
176
+ a proposed stemma rest on it.
155
177
 
156
178
  ```ts
157
179
  import { adoptEdits, adoptStemma, proposeEdits, proposeStemma } from 'linked-rolls'
@@ -167,10 +189,12 @@ const adopted = produce(edition, draft => {
167
189
 
168
190
  Why an edit was made is not read off its shape, and neither is whether
169
191
  a shift corrects an error: what is left to the editor is the
170
- motivation. On WM 225 the proposal reproduces 370 of the 414 edits the
171
- editor wrote, from what each version changes, and all of their types
172
- but the shifts the editor took for corrected errors. From the copies
173
- alone, through the proposed stemma, it reproduces 326.
192
+ motivation. Correcting an error is therefore one of the motivations
193
+ (`correctedError`), not one of the edit types, and a document written
194
+ while it was a type is read with that motivation in its place. On
195
+ WM 225 the proposal reproduces 370 of the 414 edits the editor wrote,
196
+ from what each version changes, and every type the editor gave them.
197
+ From the copies alone, through the proposed stemma, it reproduces 326.
174
198
 
175
199
  ## Format
176
200
 
@@ -182,7 +206,12 @@ https://pfefferniels.github.io/linked-rolls/, to which the w3id.org
182
206
  identifiers redirect.
183
207
 
184
208
  Beliefs are written as JSON-LD-star annotations (`@annotation`), so you
185
- need a processor that supports JSON-LD-star to read them.
209
+ need a processor that supports JSON-LD-star to read them. A statement
210
+ that belongs to a proposition set carries no belief of its own: its
211
+ annotation names the set by its `@id`, which makes the set the reifier
212
+ of every statement that names it, as RDF 1.2 allows. The set is stated
213
+ once, under `propositionSets`, as a `crminf:I4_Proposition_Set` with its
214
+ belief.
186
215
 
187
216
  ## Development
188
217
 
@@ -199,11 +228,12 @@ TypeScript 7 no longer has the compiler API it reads the source with.
199
228
 
200
229
  ## Releasing
201
230
 
202
- Raise the version in `package.json` and in `CITATION.cff`, there with
203
- the day as `date-released`, commit, and push a tag `v<version>`. The
204
- `publish.yml` workflow builds, tests and publishes to npm, then makes a
205
- GitHub release of the tag, which Zenodo archives with a DOI. A test fails
206
- while the two versions differ.
231
+ Raise the version in `package.json`, in `CITATION.cff`, there with the
232
+ day as `date-released`, and in `src/software.ts`, which names the release
233
+ in the reasonings the library records. Then commit, and push a tag
234
+ `v<version>`. The `publish.yml` workflow builds, tests and publishes to
235
+ npm, then makes a GitHub release of the tag, which Zenodo archives with
236
+ a DOI. A test fails while the versions differ.
207
237
 
208
238
  ## About
209
239
 
@@ -164,6 +164,10 @@ export declare const uncheckedMotivation: {
164
164
  * A recoding that only inserts or only deletes is the editor's: a
165
165
  * collation types no one-sided edit, so one typed so is a reading of
166
166
  * the transfer that collating again must not undo.
167
+ *
168
+ * An edit a program proposed and typed is no reading of the editor's
169
+ * either, as long as nobody has motivated it: it says so by belonging
170
+ * to the proposition set the program's run concluded (`adoptEdits`).
167
171
  * @category Collation and alignment
168
172
  */
169
173
  export declare const isCollationsOwn: (edit: Readonly<Edit>) => boolean;
@@ -1,3 +1,4 @@
1
+ import { isHeldForItself, propositionSetOf } from "../model/Assumption.js";
1
2
  import { keyOf } from "../systems/TrackerBar.js";
2
3
  import { distance, mm } from "../model/Quantity.js";
3
4
  import { partitionPoint } from "../shared/sorted.js";
@@ -153,9 +154,13 @@ export const uncheckedMotivation = {
153
154
  * A recoding that only inserts or only deletes is the editor's: a
154
155
  * collation types no one-sided edit, so one typed so is a reading of
155
156
  * the transfer that collating again must not undo.
157
+ *
158
+ * An edit a program proposed and typed is no reading of the editor's
159
+ * either, as long as nobody has motivated it: it says so by belonging
160
+ * to the proposition set the program's run concluded (`adoptEdits`).
156
161
  * @category Collation and alignment
157
162
  */
158
163
  export const isCollationsOwn = (edit) => (edit.editType === drawnByCollation && !!edit.insert?.length && !!edit.delete?.length)
159
- || (edit.editType === undefined
164
+ || ((edit.editType === undefined || propositionSetOf(edit) !== undefined)
160
165
  && (edit.motivation === undefined || edit.motivation === unchecked)
161
- && edit['@annotation'] === undefined);
166
+ && !isHeldForItself(edit));
@@ -1,7 +1,9 @@
1
1
  import { Edition } from "../model/Edition.js";
2
+ import { PropositionSet } from "../model/Assumption.js";
2
3
  import { Version } from "../model/Version.js";
3
4
  import { EditionOp } from "../ops/draft.js";
4
5
  import { StemmaProposal, Unpinned } from "./proposal.js";
6
+ import { Run } from "./record.js";
5
7
  /**
6
8
  * What adopting a proposal makes of the stemma: the versions it puts in
7
9
  * place of the edition's, and what of the edition it leaves out.
@@ -15,6 +17,12 @@ export interface Adoption {
15
17
  removed: string[];
16
18
  /** The edits the editor wrote that the adoption leaves out, and why. */
17
19
  dropped: Unpinned[];
20
+ /**
21
+ * The record of the run: the proposition set the derivations and
22
+ * edits the proposal concluded belong to, held under one belief
23
+ * whose reason is the inference the program drew.
24
+ */
25
+ propositionSet: PropositionSet;
18
26
  }
19
27
  /**
20
28
  * What adopting the proposal makes of the edition's stemma, worked out
@@ -35,13 +43,28 @@ export interface Adoption {
35
43
  * id and whatever it states beside its carriers. A version of the edition
36
44
  * nothing stands for goes, unless it states no edits and derives from a
37
45
  * version that stays, as a version known only from a recording does.
46
+ *
47
+ * What the program concluded is recorded as one proposition set, since
48
+ * the search scores whole stemmata and concludes the whole: each
49
+ * derivation it states and each edit it makes belongs to the set, which
50
+ * holds them under one belief, likely unless the run says otherwise,
51
+ * concluded by an inference that names this library and its release,
52
+ * the copies it collated, the rules it followed and, in its note, what
53
+ * it was computed with. A derivation or an edit that is the editor's,
54
+ * held under a belief of its own or saying more than a program says,
55
+ * stays the editor's and outside the set. The run gives who ran the
56
+ * program and when (`Run`).
38
57
  * @category Filiation
39
58
  */
40
- export declare const adoptionOf: (edition: Edition, proposal: StemmaProposal) => Adoption;
59
+ export declare const adoptionOf: (edition: Edition, proposal: StemmaProposal, run?: Run) => Adoption;
41
60
  /**
42
61
  * Puts the proposed stemma in place of the edition's, as `adoptionOf`
43
- * describes, and takes back a copy's statement that it carries a version
44
- * that goes. One operation, and so one undo step.
62
+ * describes, with the record of the run, and takes back a copy's
63
+ * statement that it carries a version that goes. The record of an
64
+ * earlier run whose conclusions are all replaced goes with them, and
65
+ * one of the very same run, by the same release on the same copies with
66
+ * the same result, stays as it was first recorded. One operation, and
67
+ * so one undo step.
45
68
  * @category Operations
46
69
  */
47
- export declare const adoptStemma: (proposal: StemmaProposal) => EditionOp;
70
+ export declare const adoptStemma: (proposal: StemmaProposal, run?: Run) => EditionOp;
@@ -1,15 +1,50 @@
1
1
  /** Taking a proposed stemma into the edition, as one operation. */
2
2
  import { v5 } from "uuid";
3
- import { assignReference, idOf } from "../model/Assumption.js";
3
+ import { assignReference, idOf, isHeldForItself } from "../model/Assumption.js";
4
4
  import { derivesFrom, editsOf, insertedBy, principalDerivationOf } from "../model/Version.js";
5
5
  import { systemOf } from "../systems/TrackerBar.js";
6
- import { unchecked } from "../collation/Collation.js";
6
+ import { isCollationsOwn, unchecked } from "../collation/Collation.js";
7
7
  import { versionIn } from "../lookup/lookup.js";
8
8
  import { declaring, reading } from "../ops/draft.js";
9
9
  import { dropStatements } from "../ops/copies.js";
10
10
  import { barNamed } from "./systems.js";
11
+ import { inMillimetres, keptSets, memberOf, recorded, stemmaRules } from "./record.js";
11
12
  /** The namespace the names of adopted versions and edits are minted in, so that adopting again gives them again. */
12
13
  const ADOPTED = '6f1d2c8e-93a4-4f0b-b8a1-2f4c5d7e9a31';
14
+ /** The rules the proposal followed, as far as its costs and settings carry them out. */
15
+ const rulesOf = (proposal) => [
16
+ ...(proposal.costs.gain > 1 ? [stemmaRules.sharedAddition] : []),
17
+ ...(proposal.costs.redundantLoss < 1 ? [stemmaRules.independentCleanup] : []),
18
+ ...(proposal.costs.transferLoss < 1 ? [stemmaRules.recutLoss] : []),
19
+ ...(Number.isFinite(proposal.costs.copyDeviation) ? [stemmaRules.copyDeviation] : []),
20
+ ...(proposal.costs.lostState > 0 ? [stemmaRules.lostStates] : []),
21
+ ...(proposal.sameText >= 0 ? [stemmaRules.oneText] : []),
22
+ stemmaRules.refinement,
23
+ stemmaRules.noContamination
24
+ ];
25
+ /** A number as a note gives it: to two places at most. */
26
+ const amount = (value) => String(Math.round(value * 100) / 100);
27
+ /**
28
+ * What the run of `proposeStemma` was, and what it was computed with, in
29
+ * words, so that it can be run again: the copies by siglum, the costs,
30
+ * the settings, and the margin by which the copies decide each grouping.
31
+ */
32
+ const noteOn = (edition, proposal) => {
33
+ const siglumOf = new Map(edition.copies.map(copy => [copy.id, copy.siglum ?? copy.id]));
34
+ const named = (copies) => copies.map(copy => siglumOf.get(copy) ?? copy).join(', ');
35
+ const { costs } = proposal;
36
+ const supports = proposal.supports.map(support => support.ancestor
37
+ ? `${named(support.ancestor)} above ${named(support.copies.filter(copy => !support.ancestor.includes(copy)))} by ${amount(support.margin)}`
38
+ : `${named(support.copies)} together by ${amount(support.margin)}`);
39
+ return [
40
+ `The stemma ${proposal.notation}, the best of ${proposal.scored} that ${named(proposal.copies)} admit, at a cost of ${amount(proposal.cost)}.`,
41
+ `A reading arising costs ${amount(costs.gain)}, a reading lost 1, a redundancy lost ${amount(costs.redundantLoss)}, a reading lost on the way into another system ${amount(costs.transferLoss)}, a copy lacking a reading of its version ${amount(costs.copyDeviation)}, and a lost state ${amount(costs.lostState)}.`,
42
+ `Copies of one system differing in ${proposal.sameText < 0 ? 'any reading' : `no more than ${proposal.sameText} readings`} are taken for one text; a reading of the same kind within ${inMillimetres(proposal.movedWithin)} on the other side of the root for a moved one; and ${inMillimetres(proposal.margin)} beyond a copy's text or a tear for what the copy cannot show.`,
43
+ ...(supports.length > 0 ? [`The copies decide its groupings by: ${supports.join('; ')}.`] : [])
44
+ ].join(' ');
45
+ };
46
+ /** The set the run of `proposeStemma` concluded, held with the belief the run gives it. */
47
+ const stemmaSetOf = (edition, proposal, run) => recorded('stemma', [proposal.copies, proposal.notation, proposal.costs, proposal.sameText, proposal.movedWithin, proposal.margin], { note: noteOn(edition, proposal), premises: [], used: [...proposal.copies], applies: rulesOf(proposal) }, run);
13
48
  const exchangeKey = (inserted, deleted) => `${[...inserted].sort().join(',')}/${[...deleted].sort().join(',')}`;
14
49
  /** The carriers of the reading, keeping what the edition states of those it held already. */
15
50
  const carriersOf = (reading, stated) => {
@@ -35,9 +70,21 @@ const carriersOf = (reading, stated) => {
35
70
  * id and whatever it states beside its carriers. A version of the edition
36
71
  * nothing stands for goes, unless it states no edits and derives from a
37
72
  * version that stays, as a version known only from a recording does.
73
+ *
74
+ * What the program concluded is recorded as one proposition set, since
75
+ * the search scores whole stemmata and concludes the whole: each
76
+ * derivation it states and each edit it makes belongs to the set, which
77
+ * holds them under one belief, likely unless the run says otherwise,
78
+ * concluded by an inference that names this library and its release,
79
+ * the copies it collated, the rules it followed and, in its note, what
80
+ * it was computed with. A derivation or an edit that is the editor's,
81
+ * held under a belief of its own or saying more than a program says,
82
+ * stays the editor's and outside the set. The run gives who ran the
83
+ * program and when (`Run`).
38
84
  * @category Filiation
39
85
  */
40
- export const adoptionOf = (edition, proposal) => {
86
+ export const adoptionOf = (edition, proposal, run = {}) => {
87
+ const set = stemmaSetOf(edition, proposal, run);
41
88
  const readingOf = new Map(proposal.collation.readings.map(reading => [reading.id, reading]));
42
89
  const symbolIdOf = (reading) => proposal.symbols[reading] ?? reading;
43
90
  const existing = new Map(edition.versions.flatMap(insertedBy).map(symbol => [symbol.id, symbol]));
@@ -112,16 +159,18 @@ export const adoptionOf = (edition, proposal) => {
112
159
  const key = exchangeKey(inserted, deleted);
113
160
  const same = statedBefore.get(key);
114
161
  const insert = inserts.map(reading => symbolOf(reading, proposed.id));
115
- if (same)
116
- return { ...same, ...(insert.length > 0 && { insert }), ...(deleted.length > 0 && { delete: deleted }) };
117
- return {
162
+ if (same) {
163
+ const taken = { ...same, ...(insert.length > 0 && { insert }), ...(deleted.length > 0 && { delete: deleted }) };
164
+ return isCollationsOwn(same) ? memberOf(set.id, taken) : taken;
165
+ }
166
+ return memberOf(set.id, {
118
167
  type: 'edit',
119
168
  id: v5(`edit|${id}|${key}`, ADOPTED),
120
169
  ...(recoding && { editType: 'recoding' }),
121
170
  motivation: unchecked,
122
171
  ...(insert.length > 0 && { insert }),
123
172
  ...(deleted.length > 0 && { delete: deleted })
124
- };
173
+ });
125
174
  };
126
175
  // A recoding an edit of the editor's cuts into is stated reading by reading.
127
176
  const whole = proposed.recodings.filter(recoding => ![...recoding.by.map(reading => symbolIdAt(reading, proposed.id)), ...recoding.replaced.map(reading => inheritedAt(reading, proposed.id))]
@@ -133,11 +182,13 @@ export const adoptionOf = (edition, proposal) => {
133
182
  ...proposed.inserts.filter(reading => !recoded.has(reading)).map(reading => made([reading], [], false)),
134
183
  ...proposed.deletes.filter(reading => !recoded.has(reading)).map(reading => made([], [reading], false))
135
184
  ].filter((edit) => edit !== undefined);
185
+ // The derivation is the set's, unless the editor holds it for reasons of their own.
136
186
  const parent = proposed.parent ? ids.get(proposed.parent) : undefined;
137
187
  const principal = before && principalDerivationOf(before);
188
+ const same = principal && idOf(principal) === parent ? principal : undefined;
138
189
  const derivation = parent === undefined
139
190
  ? undefined
140
- : principal && idOf(principal) === parent ? principal : assignReference(parent);
191
+ : same && isHeldForItself(same) ? same : memberOf(set.id, same ?? assignReference(parent));
141
192
  const hypotheses = (before?.basedOn ?? [])
142
193
  .filter(other => other !== principal && staying.has(idOf(other)) && idOf(other) !== parent);
143
194
  const basedOn = [...(derivation ? [derivation] : []), ...hypotheses];
@@ -171,19 +222,28 @@ export const adoptionOf = (edition, proposal) => {
171
222
  // The edition's order where a version stays, the new ones after it.
172
223
  const rank = new Map(edition.versions.map((version, index) => [version.id, index]));
173
224
  const ordered = [...pruned, ...statedOnly].sort((a, b) => (rank.get(a.id) ?? Infinity) - (rank.get(b.id) ?? Infinity));
174
- return { versions: ordered, ids: Object.fromEntries(ids), removed, dropped: proposal.unpinned };
225
+ return { versions: ordered, ids: Object.fromEntries(ids), removed, dropped: proposal.unpinned, propositionSet: set };
175
226
  };
176
227
  /**
177
228
  * Puts the proposed stemma in place of the edition's, as `adoptionOf`
178
- * describes, and takes back a copy's statement that it carries a version
179
- * that goes. One operation, and so one undo step.
229
+ * describes, with the record of the run, and takes back a copy's
230
+ * statement that it carries a version that goes. The record of an
231
+ * earlier run whose conclusions are all replaced goes with them, and
232
+ * one of the very same run, by the same release on the same copies with
233
+ * the same result, stays as it was first recorded. One operation, and
234
+ * so one undo step.
180
235
  * @category Operations
181
236
  */
182
- export const adoptStemma = (proposal) => reading(edition => {
183
- const { versions, removed } = adoptionOf(edition, proposal);
237
+ export const adoptStemma = (proposal, run = {}) => reading(edition => {
238
+ const { versions, removed, propositionSet } = adoptionOf(edition, proposal, run);
184
239
  const gone = new Set(removed);
240
+ const sets = keptSets({ ...edition, versions }, [propositionSet]);
185
241
  return draft => {
186
242
  draft.versions = versions;
187
243
  draft.copies.forEach(copy => dropStatements(copy, statement => gone.has(idOf(statement))));
244
+ if (sets)
245
+ draft.propositionSets = sets;
246
+ else
247
+ delete draft.propositionSets;
188
248
  };
189
249
  });
@@ -2,6 +2,7 @@ import { Edition } from "../model/Edition.js";
2
2
  import { Edit, EditType } from "../model/Edit.js";
3
3
  import { Millimeters } from "../model/Quantity.js";
4
4
  import { EditionOp } from "../ops/draft.js";
5
+ import { Run } from "./record.js";
5
6
  /**
6
7
  * How far a perforation may be put from where it stood for the two to be
7
8
  * one move. WM 225 moves commands by up to about 60 mm.
@@ -129,6 +130,16 @@ export declare const isRegroupable: (edit: Readonly<Edit>) => boolean;
129
130
  *
130
131
  * Applied on the same draft after `adoptStemma`, it proposes the edits of
131
132
  * the stemma adopted, as one undo step.
133
+ *
134
+ * The edits it proposes are recorded as one proposition set, held under
135
+ * one belief, likely unless the run says otherwise, whose reason is the
136
+ * inference the program drew: it names this library and its release,
137
+ * the versions it read, the rule that keeps an edit at one place and, in
138
+ * its note, the distances it grouped with, and it rests on the beliefs
139
+ * the derivations of the versions are held with. An edit of the set
140
+ * that nobody has motivated is the program's, typed and all, and a
141
+ * later proposal groups it again. The run gives who ran the program and
142
+ * when (`Run`).
132
143
  * @category Operations
133
144
  */
134
- export declare const adoptEdits: (versionIds?: readonly string[], options?: EditOptions) => EditionOp;
145
+ export declare const adoptEdits: (versionIds?: readonly string[], options?: EditOptions, run?: Run) => EditionOp;
@@ -4,7 +4,8 @@
4
4
  * group is. Why a change was made is left to the editor.
5
5
  */
6
6
  import { v5 } from "uuid";
7
- import { deletedBy, editsOf, insertedBy } from "../model/Version.js";
7
+ import { beliefIn } from "../model/Assumption.js";
8
+ import { deletedBy, editsOf, insertedBy, principalDerivationOf } from "../model/Version.js";
8
9
  import { distance, mm, subtract } from "../model/Quantity.js";
9
10
  import { trackerBarOf } from "../systems/index.js";
10
11
  import { operationOf, substitutionsBetween } from "../collation/substitution.js";
@@ -16,6 +17,7 @@ import { groupBy } from "../shared/utils.js";
16
17
  import { declaring, reading } from "../ops/draft.js";
17
18
  import { ineffectiveAmong } from "./presence.js";
18
19
  import { reads } from "./readings.js";
20
+ import { editRules, inMillimetres, keptSets, memberOf, recorded } from "./record.js";
19
21
  /**
20
22
  * How far a perforation may be put from where it stood for the two to be
21
23
  * one move. WM 225 moves commands by up to about 60 mm.
@@ -479,8 +481,17 @@ const adoptedEditsOf = (edition, version, options) => {
479
481
  // What an edit that stays leaves of a proposed one stands symbol by symbol.
480
482
  return [...inserted.map(id => made([id], [])), ...deleted.map(id => made([], [id]))];
481
483
  });
482
- return [...held, ...proposed];
484
+ return { held, proposed };
483
485
  };
486
+ /**
487
+ * What the run of `proposeEdits` was computed with, in words, so that it
488
+ * can be run again.
489
+ */
490
+ const noteOn = (settings) => 'The changes of each version against the version it derives from, grouped line by line and never across a command both texts keep. '
491
+ + `A perforation put elsewhere within ${inMillimetres(settings.shiftWithin)} is one move with the one it replaces, `
492
+ + `an Off within ${inMillimetres(settings.pairWithin)} of an On one function turned on and off again, `
493
+ + `a perforation beginning within ${inMillimetres(settings.resizeWithin)} of the one it replaces a shortened or prolonged one, `
494
+ + `and perforations within ${inMillimetres(settings.samePlaceWithin)} of each other at one place.`;
484
495
  /**
485
496
  * Puts the proposed edits (`proposeEdits`) in place of those of the
486
497
  * versions that a collation or an adoption wrote and nobody has given a
@@ -495,19 +506,35 @@ const adoptedEditsOf = (edition, version, options) => {
495
506
  *
496
507
  * Applied on the same draft after `adoptStemma`, it proposes the edits of
497
508
  * the stemma adopted, as one undo step.
509
+ *
510
+ * The edits it proposes are recorded as one proposition set, held under
511
+ * one belief, likely unless the run says otherwise, whose reason is the
512
+ * inference the program drew: it names this library and its release,
513
+ * the versions it read, the rule that keeps an edit at one place and, in
514
+ * its note, the distances it grouped with, and it rests on the beliefs
515
+ * the derivations of the versions are held with. An edit of the set
516
+ * that nobody has motivated is the program's, typed and all, and a
517
+ * later proposal groups it again. The run gives who ran the program and
518
+ * when (`Run`).
498
519
  * @category Operations
499
520
  */
500
- export const adoptEdits = (versionIds, options = {}) => reading(edition => {
521
+ export const adoptEdits = (versionIds, options = {}, run = {}) => reading(edition => {
501
522
  const targets = (versionIds ?? edition.versions.map(version => version.id))
502
523
  .map(id => versionIn(edition, id))
503
524
  .filter((version) => version !== undefined && predecessorOf(edition, version.id) !== undefined);
504
- const adopted = new Map(targets.flatMap(version => {
505
- const edits = adoptedEditsOf(edition, version, options);
525
+ const regrouped = targets.map(version => ({ version, ...adoptedEditsOf(edition, version, options) }));
526
+ const set = editSetOf(edition, regrouped, options, run);
527
+ const adopted = new Map(regrouped.flatMap(({ version, held, proposed }) => {
528
+ const edits = [...held, ...proposed.map(edit => memberOf(set.id, edit))];
506
529
  const stated = editsOf(version);
507
530
  if (edits.length === stated.length && edits.every((edit, index) => edit === stated[index]))
508
531
  return [];
509
532
  return [[version.id, { ...version, edits, motivations: declaring(version.motivations, edits) }]];
510
533
  }));
534
+ const sets = keptSets({
535
+ ...edition,
536
+ versions: edition.versions.map(version => adopted.get(version.id) ?? version)
537
+ }, [set]);
511
538
  // Each version is put in place whole: one that an operation before put
512
539
  // on the same draft, as adopting a stemma does, is no draft to write to.
513
540
  return draft => {
@@ -516,5 +543,27 @@ export const adoptEdits = (versionIds, options = {}) => reading(edition => {
516
543
  if (changed)
517
544
  draft.versions[index] = changed;
518
545
  });
546
+ if (sets)
547
+ draft.propositionSets = sets;
548
+ else
549
+ delete draft.propositionSets;
519
550
  };
520
551
  });
552
+ /**
553
+ * The set the run of `proposeEdits` concluded: the edits it proposed for
554
+ * the versions, held with the belief the run gives them. Its premises
555
+ * are the beliefs the derivations of those versions are held with, since
556
+ * what a version changes is read against the version it derives from: a
557
+ * proposed stemma's, where the derivation belongs to one.
558
+ */
559
+ const editSetOf = (edition, regrouped, options, run) => {
560
+ const settings = settingsOf(options);
561
+ const proposing = regrouped.filter(({ proposed }) => proposed.length > 0);
562
+ const premises = [...new Set(proposing.flatMap(({ version }) => {
563
+ const derivation = principalDerivationOf(version);
564
+ const belief = derivation && beliefIn(edition, derivation);
565
+ return belief ? [belief.id] : [];
566
+ }))];
567
+ const used = [...new Set(proposing.flatMap(({ version }) => [predecessorOf(edition, version.id).id, version.id]))];
568
+ return recorded('edits', [settings, premises, proposing.map(({ version, proposed }) => [version.id, proposed.map(edit => edit.id)])], { note: noteOn(settings), premises, used, applies: [editRules.onePlace] }, run);
569
+ };
@@ -9,6 +9,7 @@ export { defaultCosts, type Costs, type Scored, type Support } from "./search.js
9
9
  export { transfersOf, systemAbove } from "./systems.js";
10
10
  export { proposeStemma, defaultMovedWithin, type FiliationOptions, type ProposedVersion, type StemmaProposal, type Unpinned } from "./proposal.js";
11
11
  export { adoptionOf, adoptStemma, type Adoption } from "./adoption.js";
12
+ export { stemmaRules, editRules, type Run } from "./record.js";
12
13
  export { stemmaChanges, type VersionChange } from "./changes.js";
13
14
  export { sharedTexts, differencesBetween, defaultSameText, type SharedText, type KeptApart } from "./texts.js";
14
15
  export { proposeEdits, adoptEdits, isRegroupable, defaultShiftWithin, defaultPairWithin, defaultResizeWithin, defaultSamePlaceWithin, type EditOptions, type EditGrouping, type ProposedEdit } from "./edits.js";
@@ -9,6 +9,7 @@ export { defaultCosts } from "./search.js";
9
9
  export { transfersOf, systemAbove } from "./systems.js";
10
10
  export { proposeStemma, defaultMovedWithin } from "./proposal.js";
11
11
  export { adoptionOf, adoptStemma } from "./adoption.js";
12
+ export { stemmaRules, editRules } from "./record.js";
12
13
  export { stemmaChanges } from "./changes.js";
13
14
  export { sharedTexts, differencesBetween, defaultSameText } from "./texts.js";
14
15
  export { proposeEdits, adoptEdits, isRegroupable, defaultShiftWithin, defaultPairWithin, defaultResizeWithin, defaultSamePlaceWithin } from "./edits.js";
@@ -0,0 +1,79 @@
1
+ import { ActorAssignment, Assumption, Certainty, PropositionSet, TimeSpan } from "../model/Assumption.js";
2
+ import { Edition } from "../model/Edition.js";
3
+ /**
4
+ * The published rules the proposal of a stemma follows, by IRI, each
5
+ * under the cost or the step that carries it out. They are the
6
+ * editorial principles of the edition of WM 225, which are suggested
7
+ * for the filiation of piano rolls at large.
8
+ * @category Filiation
9
+ */
10
+ export declare const stemmaRules: {
11
+ /** An addition at the same place is not made twice independently, so a reading arising costs ten times one lost. */
12
+ readonly sharedAddition: 'https://welte225.org/rules#shared-addition';
13
+ /** Redundancies are cleaned up in two branches independently, so losing one counts for less. */
14
+ readonly independentCleanup: 'https://welte225.org/rules#independent-cleanup';
15
+ /** A re-cut for another system was an editorial pass of its own, so what it lacks counts for less. */
16
+ readonly recutLoss: 'https://welte225.org/rules#recut-loss';
17
+ /** A copy may fail to show a reading of the version it carries. */
18
+ readonly copyDeviation: 'https://welte225.org/rules#copy-deviation';
19
+ /** A lost state is posited only where shared additions require it. */
20
+ readonly lostStates: 'https://welte225.org/rules#lost-states';
21
+ /** Copies of one system that differ in a few readings carry one text. */
22
+ readonly oneText: 'https://welte225.org/rules#one-text';
23
+ /** Later versions are the more refined, which orients the root. */
24
+ readonly refinement: 'https://welte225.org/rules#refinement';
25
+ /** A version derives from one exemplar, so a stemma is a tree. */
26
+ readonly noContamination: 'https://welte225.org/rules#no-contamination';
27
+ };
28
+ /**
29
+ * The published rules the proposal of edits follows, by IRI.
30
+ * @category Filiation
31
+ */
32
+ export declare const editRules: {
33
+ /** An edit stays at one place: it runs across no command both texts keep. */
34
+ readonly onePlace: 'https://welte225.org/rules#one-place';
35
+ };
36
+ /**
37
+ * What is known of a run of a program beside what it computed: who ran
38
+ * it, when, how firmly what it concluded is held, and by which
39
+ * published rules. An adoption records the run with it; whatever is left
40
+ * out is not stated.
41
+ * @category Filiation
42
+ */
43
+ export interface Run {
44
+ /** Who ran the program and so holds what it concluded: the editor. */
45
+ actor?: ActorAssignment;
46
+ /** When it ran. */
47
+ date?: TimeSpan;
48
+ /** How firmly what it concluded is held: likely, where nothing else is said. */
49
+ certainty?: Certainty;
50
+ /** The rules it followed, by IRI, in place of those the library names (`stemmaRules`, `editRules`). */
51
+ applies?: string[];
52
+ }
53
+ /** What a recorded run concluded besides its statements: what the inference says of itself. */
54
+ export interface Concluded {
55
+ /** What the run is, and what it was computed with, in words. */
56
+ note: string;
57
+ premises: string[];
58
+ used: string[];
59
+ applies: string[];
60
+ }
61
+ /**
62
+ * The proposition set of a run, with its belief and the inference it
63
+ * rests on. Its id follows from what it is given, so that recording the
64
+ * same run again gives the same set.
65
+ */
66
+ export declare const recorded: (kind: string, identity: unknown, concluded: Concluded, run: Run) => PropositionSet;
67
+ /** The statement as one of the set: its annotation names the set, and it carries no belief of its own. */
68
+ export declare const memberOf: <T extends Assumption>(set: string, statement: T) => T;
69
+ /**
70
+ * The sets the edition keeps, with those given added where it holds none
71
+ * with their id: a set already recorded stays as it was recorded, by
72
+ * whom and when it was first concluded. Kept are the sets a statement
73
+ * belongs to, and those whose belief the reasons of another kept
74
+ * statement or set name as a premise. A set nothing names any longer is
75
+ * the record of a run whose conclusions have all been replaced.
76
+ */
77
+ export declare const keptSets: (edition: Edition, adding: readonly PropositionSet[]) => PropositionSet[] | undefined;
78
+ /** A distance in millimetres as a note gives it. */
79
+ export declare const inMillimetres: (value: number) => string;