linked-rolls 0.74.0 → 0.76.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.
Files changed (42) hide show
  1. package/README.md +62 -26
  2. package/lib/collation/Collation.d.ts +12 -42
  3. package/lib/collation/Collation.js +15 -48
  4. package/lib/filiation/adoption.d.ts +23 -4
  5. package/lib/filiation/adoption.js +81 -23
  6. package/lib/filiation/edits.d.ts +35 -19
  7. package/lib/filiation/edits.js +68 -34
  8. package/lib/filiation/index.d.ts +2 -1
  9. package/lib/filiation/index.js +2 -1
  10. package/lib/filiation/record.d.ts +49 -0
  11. package/lib/filiation/record.js +49 -0
  12. package/lib/index.d.ts +1 -0
  13. package/lib/index.js +1 -0
  14. package/lib/io/asJsonLd.js +5 -1
  15. package/lib/io/importJsonLd.js +42 -4
  16. package/lib/io/migrate.d.ts +7 -0
  17. package/lib/io/migrate.js +196 -2
  18. package/lib/model/Agent.d.ts +3 -1
  19. package/lib/model/Assumption.d.ts +8 -1
  20. package/lib/model/Assumption.js +2 -1
  21. package/lib/model/Edit.d.ts +76 -17
  22. package/lib/model/Edit.js +52 -3
  23. package/lib/model/Edition.d.ts +32 -3
  24. package/lib/model/RollCopy.js +1 -1
  25. package/lib/model/Version.d.ts +3 -13
  26. package/lib/model/Version.js +7 -2
  27. package/lib/ops/collation.d.ts +8 -4
  28. package/lib/ops/collation.js +40 -23
  29. package/lib/ops/copies.js +1 -2
  30. package/lib/ops/draft.d.ts +1 -9
  31. package/lib/ops/draft.js +0 -12
  32. package/lib/ops/edits.d.ts +20 -3
  33. package/lib/ops/edits.js +36 -15
  34. package/lib/ops/index.d.ts +1 -1
  35. package/lib/ops/index.js +1 -1
  36. package/lib/ops/versions.d.ts +3 -2
  37. package/lib/ops/versions.js +4 -5
  38. package/lib/schema.json +60 -64
  39. package/lib/software.d.ts +12 -0
  40. package/lib/software.js +10 -0
  41. package/lib/spec/context.json +8 -5
  42. package/package.json +1 -1
@@ -1,21 +1,24 @@
1
1
  /**
2
2
  * The edits of a version, proposed from what it inserts and deletes:
3
- * which of its changes belong together, and what kind of change each
4
- * group is. Why a change was made is left to the editor.
3
+ * which of its changes belong together, and what shape each group
4
+ * takes, from which the motivation a program can give it follows. Why
5
+ * a musical change was made is left to the editor.
5
6
  */
6
7
  import { v5 } from "uuid";
8
+ import { motivationProposedFor, musicalChange, technicalChange } from "../model/Edit.js";
7
9
  import { deletedBy, editsOf, insertedBy } from "../model/Version.js";
8
10
  import { distance, mm, subtract } from "../model/Quantity.js";
9
11
  import { trackerBarOf } from "../systems/index.js";
10
12
  import { operationOf, substitutionsBetween } from "../collation/substitution.js";
11
- import { isCollationsOwn, unchecked } from "../collation/Collation.js";
13
+ import { isCollationsOwn } from "../collation/Collation.js";
12
14
  import { predecessorOf } from "../analysis/stemma.js";
13
15
  import { placeOf, snapshotOf } from "../analysis/text.js";
14
16
  import { symbolsIn, versionIn } from "../lookup/lookup.js";
15
17
  import { groupBy } from "../shared/utils.js";
16
- import { declaring, reading } from "../ops/draft.js";
18
+ import { reading } from "../ops/draft.js";
17
19
  import { ineffectiveAmong } from "./presence.js";
18
20
  import { reads } from "./readings.js";
21
+ import { editRules, madeWith } from "./record.js";
19
22
  /**
20
23
  * How far a perforation may be put from where it stood for the two to be
21
24
  * one move. WM 225 moves commands by up to about 60 mm.
@@ -278,8 +281,8 @@ const rowsAmong = (groups, within) => {
278
281
  };
279
282
  /** Whether the bar spells an accent with exactly these commands. */
280
283
  const spellsAccent = (bar, types) => (bar?.accents ?? []).some(accent => sameSequence(types, accent));
281
- /** What kind of change a group is, where its shape says. */
282
- const typeOf = (group, bar, accentsAt, options) => {
284
+ /** The shape a group of changes takes, where it is one the program knows. */
285
+ const shapeOfGroup = (group, bar, accentsAt, options) => {
283
286
  const inserted = inOrder(ofSide(group.changes, 'insert'));
284
287
  const deleted = inOrder(ofSide(group.changes, 'delete'));
285
288
  if (group.grouping === 'substitution' || group.changes.some(change => change.foreign))
@@ -343,8 +346,8 @@ const settingsOf = (options) => ({
343
346
  * punched across three lines or more at one place are one edit, as a
344
347
  * cancel row is.
345
348
  *
346
- * The type is read off the shape: a pair the bar spells an accent with is
347
- * an additional accent, as is a note set off from its chord by a few
349
+ * The shape is read off the group: a pair the bar spells an accent with
350
+ * is an additional accent, as is a note set off from its chord by a few
348
351
  * millimetres where the version puts an accent's On; a perforation put
349
352
  * elsewhere is a shift, or where it begins about where the other did,
350
353
  * shortened or prolonged; a note struck again is shortened, two tied
@@ -427,11 +430,11 @@ export const proposeEdits = (edition, versionId, options = {}) => {
427
430
  const proposed = [...substituted, ...recoded, ...lines, ...rows]
428
431
  .sort((a, b) => byPlace(inOrder(a.changes)[0], inOrder(b.changes)[0]))
429
432
  .map((group) => {
430
- const editType = typeOf(group, bar, accentsAt, settings);
433
+ const shape = shapeOfGroup(group, bar, accentsAt, settings);
431
434
  return {
432
435
  insert: inOrder(ofSide(group.changes, 'insert')).map(change => change.symbol.id),
433
436
  delete: inOrder(ofSide(group.changes, 'delete')).map(change => change.symbol.id),
434
- ...(editType && { editType }),
437
+ ...(shape && { shape }),
435
438
  grouping: group.grouping
436
439
  };
437
440
  });
@@ -441,60 +444,87 @@ export const proposeEdits = (edition, versionId, options = {}) => {
441
444
  const EDITS = '3c9e0f62-1d4b-4a7e-9b55-8e2f7a6d1c04';
442
445
  const exchangeKey = (inserted, deleted) => `${[...inserted].sort().join(',')}/${[...deleted].sort().join(',')}`;
443
446
  /**
444
- * Whether the edit is left for a proposal to group again: one a
445
- * collation or an adoption wrote, that nobody has given a motivation.
447
+ * The motivation a program states for each change the version makes,
448
+ * by the id of the symbol it inserts or deletes: the one the shape of
449
+ * the proposed edit it belongs to calls for (`motivationProposedFor`),
450
+ * musical or technical. A version that derives from none makes no
451
+ * change, and its symbols are given none.
446
452
  * @category Filiation
447
453
  */
448
- export const isRegroupable = (edit) => isCollationsOwn(edit) && (edit.motivation === undefined || edit.motivation === unchecked);
454
+ export const proposedMotivations = (edition, versionId, options = {}) => {
455
+ const motivations = new Map();
456
+ if (!predecessorOf(edition, versionId))
457
+ return motivations;
458
+ for (const edit of proposeEdits(edition, versionId, options)) {
459
+ const motivation = motivationProposedFor(edit.shape);
460
+ for (const id of [...edit.insert, ...edit.delete])
461
+ motivations.set(id, motivation);
462
+ }
463
+ return motivations;
464
+ };
465
+ /**
466
+ * The motivation a program states for an edit making these changes:
467
+ * the generic musical one where any of them is a musical change, or one
468
+ * the proposal does not see, and the technical one where all are
469
+ * technical.
470
+ */
471
+ export const motivationOfChanges = (motivations, symbolIds) => symbolIds.some(id => (motivations.get(id) ?? musicalChange) === musicalChange) ? musicalChange : technicalChange;
449
472
  /**
450
473
  * The edits the version comes to state when the proposal is adopted:
451
474
  * those it keeps, and those put in place of the rest.
452
475
  */
453
476
  const adoptedEditsOf = (edition, version, options) => {
454
477
  const edits = editsOf(version);
455
- const held = edits.filter(edit => !isRegroupable(edit));
478
+ const held = edits.filter(edit => !isCollationsOwn(edit));
456
479
  const spoken = new Set(held.flatMap(edit => [...(edit.insert ?? []).map(symbol => symbol.id), ...(edit.delete ?? [])]));
457
480
  const symbolOf = new Map(insertedBy(version).map(symbol => [symbol.id, symbol]));
458
481
  const stated = new Map(edits.map(edit => [exchangeKey((edit.insert ?? []).map(symbol => symbol.id), edit.delete ?? []), edit]));
459
- const made = (inserted, deleted, editType) => {
482
+ const made = (inserted, deleted, motivation) => {
460
483
  const key = exchangeKey(inserted, deleted);
461
484
  const same = stated.get(key);
485
+ // An edit over the same changes stays, with the kind of change it states, which the editor may have said.
462
486
  if (same)
463
- return same.editType || !editType ? same : { ...same, editType };
487
+ return same.motivation === undefined ? { ...same, motivation } : same;
464
488
  return {
465
489
  type: 'edit',
466
490
  id: v5(`edit|${version.id}|${key}`, EDITS),
467
- ...(editType && { editType }),
468
- motivation: unchecked,
491
+ motivation,
469
492
  ...(inserted.length > 0 && { insert: inserted.map(id => symbolOf.get(id)) }),
470
493
  ...(deleted.length > 0 && { delete: [...deleted] })
471
494
  };
472
495
  };
473
496
  const proposed = proposeEdits(edition, version.id, options).flatMap(edit => {
497
+ const motivation = motivationProposedFor(edit.shape);
474
498
  const free = (ids) => ids.filter(id => !spoken.has(id));
475
499
  const [inserted, deleted] = [free(edit.insert), free(edit.delete)];
476
500
  if (inserted.length === edit.insert.length && deleted.length === edit.delete.length) {
477
- return [made(inserted, deleted, edit.editType)];
501
+ return [made(inserted, deleted, motivation)];
478
502
  }
479
503
  // What an edit that stays leaves of a proposed one stands symbol by symbol.
480
- return [...inserted.map(id => made([id], [])), ...deleted.map(id => made([], [id]))];
504
+ return [...inserted.map(id => made([id], [], motivation)), ...deleted.map(id => made([], [id], motivation))];
481
505
  });
482
- return [...held, ...proposed];
506
+ return { held, proposed };
483
507
  };
484
508
  /**
485
- * Puts the proposed edits (`proposeEdits`) in place of those of the
486
- * versions that a collation or an adoption wrote and nobody has given a
487
- * motivation, every version but the root where none are named. An edit
488
- * someone motivated, typed beyond what a collation types, or held under a
489
- * belief stays as it is, and what a proposed edit shares with it stands
490
- * symbol by symbol beside it. An edit stated already over the very same
491
- * symbols as a proposed one is kept with its id and what was written on
492
- * it, the proposed type filled in where it states none; the others are
493
- * new, with ids that adopting again gives again, and marked unchecked,
494
- * so that what is left to the editor is to say why.
509
+ * Puts the proposed edits (`proposeEdits`) in place of the program's
510
+ * edits of the versions, every version but the root where none are
511
+ * named: those that say nothing of why they were made, or only that they
512
+ * are musical or technical changes (`isCollationsOwn`). An edit with a
513
+ * motivation of the editor's or held under a belief stays as it is, and
514
+ * what a proposed edit shares with it stands symbol by symbol beside it.
515
+ * An edit stated already over the very same symbols as a proposed one
516
+ * is kept with its id and the kind of change it states, which the
517
+ * editor may have said otherwise than the program read it; the others
518
+ * are new, with ids that adopting again gives again. An edit that
519
+ * states no kind has the motivation its shape calls for
520
+ * (`motivationProposedFor`): the generic musical one for a musical
521
+ * change, whose reason is left to the editor, and the technical one for
522
+ * a technical change.
495
523
  *
496
524
  * Applied on the same draft after `adoptStemma`, it proposes the edits of
497
- * the stemma adopted, as one undo step.
525
+ * the stemma adopted, as one undo step. The edition's creation comes to
526
+ * name the rule that keeps an edit at one place among its principles,
527
+ * and this release among its software.
498
528
  * @category Operations
499
529
  */
500
530
  export const adoptEdits = (versionIds, options = {}) => reading(edition => {
@@ -502,12 +532,14 @@ export const adoptEdits = (versionIds, options = {}) => reading(edition => {
502
532
  .map(id => versionIn(edition, id))
503
533
  .filter((version) => version !== undefined && predecessorOf(edition, version.id) !== undefined);
504
534
  const adopted = new Map(targets.flatMap(version => {
505
- const edits = adoptedEditsOf(edition, version, options);
535
+ const { held, proposed } = adoptedEditsOf(edition, version, options);
536
+ const edits = [...held, ...proposed];
506
537
  const stated = editsOf(version);
507
538
  if (edits.length === stated.length && edits.every((edit, index) => edit === stated[index]))
508
539
  return [];
509
- return [[version.id, { ...version, edits, motivations: declaring(version.motivations, edits) }]];
540
+ return [[version.id, { ...version, edits }]];
510
541
  }));
542
+ const creation = madeWith(edition.creation, [editRules.onePlace]);
511
543
  // Each version is put in place whole: one that an operation before put
512
544
  // on the same draft, as adopting a stemma does, is no draft to write to.
513
545
  return draft => {
@@ -516,5 +548,7 @@ export const adoptEdits = (versionIds, options = {}) => reading(edition => {
516
548
  if (changed)
517
549
  draft.versions[index] = changed;
518
550
  });
551
+ if (adopted.size > 0)
552
+ draft.creation = creation;
519
553
  };
520
554
  });
@@ -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 } 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
- export { proposeEdits, adoptEdits, isRegroupable, defaultShiftWithin, defaultPairWithin, defaultResizeWithin, defaultSamePlaceWithin, type EditOptions, type EditGrouping, type ProposedEdit } from "./edits.js";
15
+ export { proposeEdits, adoptEdits, proposedMotivations, 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
- export { proposeEdits, adoptEdits, isRegroupable, defaultShiftWithin, defaultPairWithin, defaultResizeWithin, defaultSamePlaceWithin } from "./edits.js";
15
+ export { proposeEdits, adoptEdits, proposedMotivations, defaultShiftWithin, defaultPairWithin, defaultResizeWithin, defaultSamePlaceWithin } from "./edits.js";
@@ -0,0 +1,49 @@
1
+ /**
2
+ * What adopting a proposal records of how it was found: the published
3
+ * rules the proposal follows, among the principles of the edition, and
4
+ * the release of this library, among the software it was made with.
5
+ */
6
+ import { EditionCreation } from "../model/Edition.js";
7
+ /**
8
+ * The published rules the proposal of a stemma follows, by IRI, each
9
+ * under the cost or the step that carries it out. They are the
10
+ * editorial principles of the edition of WM 225, which are suggested
11
+ * for the filiation of piano rolls at large.
12
+ * @category Filiation
13
+ */
14
+ export declare const stemmaRules: {
15
+ /** An addition at the same place is not made twice independently, so a reading arising costs ten times one lost. */
16
+ readonly sharedAddition: 'https://welte225.org/rules#shared-addition';
17
+ /** Redundancies are cleaned up in two branches independently, so losing one counts for less. */
18
+ readonly independentCleanup: 'https://welte225.org/rules#independent-cleanup';
19
+ /** A re-cut for another system was an editorial pass of its own, so what it lacks counts for less. */
20
+ readonly recutLoss: 'https://welte225.org/rules#recut-loss';
21
+ /** A copy may fail to show a reading of the version it carries. */
22
+ readonly copyDeviation: 'https://welte225.org/rules#copy-deviation';
23
+ /** A lost state is posited only where shared additions require it. */
24
+ readonly lostStates: 'https://welte225.org/rules#lost-states';
25
+ /** Copies of one system that differ in a few readings carry one text. */
26
+ readonly oneText: 'https://welte225.org/rules#one-text';
27
+ /** Later versions are the more refined, which orients the root. */
28
+ readonly refinement: 'https://welte225.org/rules#refinement';
29
+ /** A version derives from one exemplar, so a stemma is a tree. */
30
+ readonly noContamination: 'https://welte225.org/rules#no-contamination';
31
+ };
32
+ /**
33
+ * The published rules the proposal of edits follows, by IRI.
34
+ * @category Filiation
35
+ */
36
+ export declare const editRules: {
37
+ /** An edit stays at one place: it runs across no command both texts keep. */
38
+ readonly onePlace: 'https://welte225.org/rules#one-place';
39
+ };
40
+ /**
41
+ * The edition's creation as adopting a proposal leaves it. Its
42
+ * principles come to name the rules the proposal follows, in place of
43
+ * those given to be replaced, which are the rules of the same kind that
44
+ * it no longer follows; the principles the editor named stay. Its
45
+ * software comes to name this release of the library, in place of any
46
+ * other release of it, so that the release named finds again what the
47
+ * edition states.
48
+ */
49
+ export declare const madeWith: (creation: Readonly<EditionCreation>, rules: readonly string[], replacing?: readonly string[]) => EditionCreation;
@@ -0,0 +1,49 @@
1
+ import { linkedRolls } from "../software.js";
2
+ /**
3
+ * The published rules the proposal of a stemma follows, by IRI, each
4
+ * under the cost or the step that carries it out. They are the
5
+ * editorial principles of the edition of WM 225, which are suggested
6
+ * for the filiation of piano rolls at large.
7
+ * @category Filiation
8
+ */
9
+ export const stemmaRules = {
10
+ /** An addition at the same place is not made twice independently, so a reading arising costs ten times one lost. */
11
+ sharedAddition: 'https://welte225.org/rules#shared-addition',
12
+ /** Redundancies are cleaned up in two branches independently, so losing one counts for less. */
13
+ independentCleanup: 'https://welte225.org/rules#independent-cleanup',
14
+ /** A re-cut for another system was an editorial pass of its own, so what it lacks counts for less. */
15
+ recutLoss: 'https://welte225.org/rules#recut-loss',
16
+ /** A copy may fail to show a reading of the version it carries. */
17
+ copyDeviation: 'https://welte225.org/rules#copy-deviation',
18
+ /** A lost state is posited only where shared additions require it. */
19
+ lostStates: 'https://welte225.org/rules#lost-states',
20
+ /** Copies of one system that differ in a few readings carry one text. */
21
+ oneText: 'https://welte225.org/rules#one-text',
22
+ /** Later versions are the more refined, which orients the root. */
23
+ refinement: 'https://welte225.org/rules#refinement',
24
+ /** A version derives from one exemplar, so a stemma is a tree. */
25
+ noContamination: 'https://welte225.org/rules#no-contamination'
26
+ };
27
+ /**
28
+ * The published rules the proposal of edits follows, by IRI.
29
+ * @category Filiation
30
+ */
31
+ export const editRules = {
32
+ /** An edit stays at one place: it runs across no command both texts keep. */
33
+ onePlace: 'https://welte225.org/rules#one-place'
34
+ };
35
+ /**
36
+ * The edition's creation as adopting a proposal leaves it. Its
37
+ * principles come to name the rules the proposal follows, in place of
38
+ * those given to be replaced, which are the rules of the same kind that
39
+ * it no longer follows; the principles the editor named stay. Its
40
+ * software comes to name this release of the library, in place of any
41
+ * other release of it, so that the release named finds again what the
42
+ * edition states.
43
+ */
44
+ export const madeWith = (creation, rules, replacing = []) => {
45
+ const replaced = new Set(replacing);
46
+ const principles = [...new Set([...(creation.principles ?? []).filter(rule => !replaced.has(rule)), ...rules])];
47
+ const software = [...(creation.software ?? []).filter(program => program.name !== linkedRolls.name), linkedRolls];
48
+ return { ...creation, principles, software };
49
+ };
package/lib/index.d.ts CHANGED
@@ -61,6 +61,7 @@ export * from './analysis/witnesses.js';
61
61
  export { alignFeatures, ALIGNMENT_METHOD, fromAxis, ownFeaturesOf, toAxis, tooShortToShorten, type AlignmentResult } from './collation/alignment.js';
62
62
  export * from './collation/statistics.js';
63
63
  export * from './model/Edition.js';
64
+ export { linkedRolls } from './software.js';
64
65
  export * from './lookup/lookup.js';
65
66
  export * from './lookup/paths.js';
66
67
  export * from './analysis/stemma.js';
package/lib/index.js CHANGED
@@ -24,6 +24,7 @@ export * from './analysis/witnesses.js';
24
24
  export { alignFeatures, ALIGNMENT_METHOD, fromAxis, ownFeaturesOf, toAxis, tooShortToShorten } from './collation/alignment.js';
25
25
  export * from './collation/statistics.js';
26
26
  export * from './model/Edition.js';
27
+ export { linkedRolls } from './software.js';
27
28
  export * from './lookup/lookup.js';
28
29
  export * from './lookup/paths.js';
29
30
  export * from './analysis/stemma.js';
@@ -114,7 +114,11 @@ const isDoubtedReference = (value) => isRecord(value)
114
114
  */
115
115
  const quote = (subject, key, reference, listed) => {
116
116
  const { '@annotation': { '@id': annotation, ...about }, ...object } = reference;
117
- return { '@id': { '@id': subject, [key]: listed ? [object] : object }, annotation, ...about };
117
+ return {
118
+ '@id': { '@id': subject, [key]: listed ? [object] : object },
119
+ annotation,
120
+ ...about
121
+ };
118
122
  };
119
123
  /** The value a node states under a key, less the doubted references, which go to the quoted. */
120
124
  const unquoted = (subject, key, value, quoted) => {
@@ -1,7 +1,9 @@
1
1
  import { derivedKeys } from "./asJsonLd.js";
2
- import { migrate, plainCopyId } from "./migrate.js";
2
+ import { migrate, plainCopyId, uncheckedMotivation } from "./migrate.js";
3
3
  import { isDateString } from "../shared/utils.js";
4
4
  import { putOnAxis } from "../collation/alignment.js";
5
+ import { editsOf } from "../model/Version.js";
6
+ import { motivationOfChanges, proposedMotivations } from "../filiation/edits.js";
5
7
  import schema from "../schema.json" with { type: 'json' };
6
8
  export const importDate = (str) => {
7
9
  const [y, m, d] = str.split('-').map(s => parseInt(s, 10));
@@ -83,9 +85,21 @@ const withoutEmptyCreations = (json) => Array.isArray(json.versions)
83
85
  : json;
84
86
  /** A statement an export quoted rather than stated: an included node whose id is a triple. */
85
87
  const isQuotedStatement = (node) => node !== null && typeof node === 'object' && node['@id'] !== null && typeof node['@id'] === 'object';
86
- /** The reference a quoted statement made, annotated again with the belief the export set beside it. */
88
+ /**
89
+ * A belief named by its id alone, as a quoted statement of a document
90
+ * written before 0.76 named the belief of the proposition set it
91
+ * belonged to, which was stated with the set (`migrate`).
92
+ */
93
+ const isNamedBelief = (belief) => belief !== null && typeof belief === 'object' && Object.keys(belief).every(key => key === '@id');
94
+ /**
95
+ * The reference a quoted statement made, annotated again with the belief
96
+ * the export set beside it, or where it belonged to a proposition set,
97
+ * naming the set again for `migrate` to resolve.
98
+ */
87
99
  const referenceOf = (statement) => {
88
- const { '@id': { '@id': subject, ...made }, annotation, ...about } = statement;
100
+ const { '@id': { '@id': subject, ...made }, annotation, ...stated } = statement;
101
+ const { belief, ...rest } = stated;
102
+ const about = isNamedBelief(belief) ? rest : stated;
89
103
  const [key, value] = Object.entries(made)[0];
90
104
  const listed = Array.isArray(value);
91
105
  return {
@@ -142,5 +156,29 @@ export const importJsonLd = (json) => {
142
156
  ? context.find((c) => c['@base'])?.['@base'] || ''
143
157
  : '';
144
158
  (edition.copies ?? []).forEach(putOnAxis);
145
- return edition;
159
+ return withKindsReadOffShapes(edition);
160
+ };
161
+ /**
162
+ * Gives an edit that a document written before 0.76 left unchecked the
163
+ * motivation its shape calls for, read in the edition as it stands: the
164
+ * generic musical one or the technical one (`migrate`). The
165
+ * shapes are read off a copy, since what reads an edition settles it
166
+ * for good, and the edition an import returns is the caller's to change.
167
+ */
168
+ const withKindsReadOffShapes = (edition) => {
169
+ if (!(edition.versions ?? []).some(version => editsOf(version).some(edit => edit.motivation === uncheckedMotivation)))
170
+ return edition;
171
+ const probe = structuredClone(edition);
172
+ const versions = edition.versions.map(version => {
173
+ if (!editsOf(version).some(edit => edit.motivation === uncheckedMotivation))
174
+ return version;
175
+ const motivations = proposedMotivations(probe, version.id);
176
+ return {
177
+ ...version,
178
+ edits: editsOf(version).map(edit => edit.motivation === uncheckedMotivation
179
+ ? { ...edit, motivation: motivationOfChanges(motivations, [...(edit.insert ?? []).map(symbol => symbol.id), ...(edit.delete ?? [])]) }
180
+ : edit)
181
+ };
182
+ });
183
+ return { ...edition, versions };
146
184
  };
@@ -13,6 +13,13 @@
13
13
  type Json = any;
14
14
  /** A copy's id without the `copy/` that documents of earlier releases put before it. */
15
15
  export declare const plainCopyId: (id: string) => string;
16
+ /**
17
+ * The motivation an edit of a version that derives from another is left
18
+ * with where only the edition can tell what kind of change it is: one
19
+ * that states neither a kind nor a reason, as a collation once left
20
+ * them. `importJsonLd` reads its kind off its shape (`proposedMotivations`).
21
+ */
22
+ export declare const uncheckedMotivation = "unchecked";
16
23
  /**
17
24
  * Brings the JSON of an edition written by an earlier release of the
18
25
  * format up to the current shape. A document that is current already