rapier-markdown-kit 1.1.90 → 1.1.92
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 +71 -37
- package/dist/agent/diff.mjs +52 -0
- package/dist/kit/ledger/authorship.mjs +3 -3
- package/dist/kit/ledger/carried.mjs +10 -10
- package/dist/kit/ledger/format.mjs +44 -28
- package/dist/kit/ledger/history.mjs +526 -0
- package/dist/kit/ledger/journal-records.mjs +216 -45
- package/dist/kit/ledger/merge.mjs +191 -51
- package/dist/kit/ledger/transport.mjs +318 -0
- package/dist/kit/rapier-ledger.mjs +12 -2
- package/dist/kit/render-markdown.mjs +227 -109
- package/dist/kit/render-styles.mjs +5 -4
- package/dist/kit/render-work.mjs +145 -0
- package/dist/kit/render.mjs +249 -76
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
# rapier-markdown-kit
|
|
2
2
|
|
|
3
|
-
Read,
|
|
4
|
-
and colour.
|
|
3
|
+
Read, render and write Self-contained Markdown in browsers and Node: one `.md` file with pictures, editable SVG
|
|
4
|
+
drawings, layout and colour. Build portable documents for people and agents. Will/1 regions mark what an agent
|
|
5
|
+
may edit, append to or preserve. MIT, no dependencies.
|
|
5
6
|
|
|
6
7
|
- Everything past CommonMark and GFM is an HTML comment other readers ignore or an ordinary reference
|
|
7
8
|
definition, so every Markdown app still reads the file.
|
|
@@ -15,6 +16,10 @@ To show documents in your app without writing a renderer, frame the read-only re
|
|
|
15
16
|
`npm install rapier-embed` (`build: 'reader'`). To hand a person the whole editor around their document as one
|
|
16
17
|
offline file, run `npx rapier-html notes.md`.
|
|
17
18
|
|
|
19
|
+
In the editor, an agent edits live beside the person or while they are away. Rapier shows its presence and changes;
|
|
20
|
+
the person taps a change to see what was there before and undoes anything. `comparison.present` lets the agent
|
|
21
|
+
deliberately show a diff. The kit supplies the portable source and history those tools use.
|
|
22
|
+
|
|
18
23
|
```sh
|
|
19
24
|
npm install rapier-markdown-kit
|
|
20
25
|
```
|
|
@@ -27,6 +32,18 @@ import {parseWill} from 'rapier-markdown-kit/will'; // Will/1 markers: kee
|
|
|
27
32
|
import {parseAssets} from 'rapier-markdown-kit/assets'; // the picture appendix
|
|
28
33
|
```
|
|
29
34
|
|
|
35
|
+
## Choose a Rapier workflow
|
|
36
|
+
|
|
37
|
+
Rapier is a phone-first Markdown editor for notes, diagrams, drawing and watercolor painting. It runs offline
|
|
38
|
+
as one HTML file on Android, Web and Windows.
|
|
39
|
+
|
|
40
|
+
1. **Work together live.** Connect to `https://mcp.rapier.website/mcp`, then call `rapier.open` to share one document with the person.
|
|
41
|
+
2. **Deliver an offline editor.** Run `npx rapier-html notes.md` to put the document and editor in one HTML file ([rapier-html](https://github.com/jackskip22/rapier-plugins/tree/main/npm/rapier-html)).
|
|
42
|
+
3. **Embed in your app.** Install [rapier-embed](https://github.com/jackskip22/rapier-plugins/tree/main/npm/rapier-embed); mount the editor or reader, with your app's agent over WebMCP in the editor.
|
|
43
|
+
4. **Keep portable Markdown.** Use [rapier-markdown-kit](https://github.com/jackskip22/rapier-plugins/tree/main/npm/rapier-markdown-kit) for pictures, editable SVG drawings and layout in one `.md` file. Respect [Will/1](https://github.com/jackskip22/will) `keep`, `append` and `edit` regions.
|
|
44
|
+
5. **Encode JPEG XL.** Install [rapier-jxl](https://github.com/jackskip22/rapier-jxl) to encode pixels, photographs and existing JPEGs in JavaScript.
|
|
45
|
+
6. **Host the door.** Run [rapier-server](https://github.com/jackskip22/rapier/tree/main/server) over your own folder or S3-compatible bucket.
|
|
46
|
+
|
|
30
47
|
## The shared renderer
|
|
31
48
|
|
|
32
49
|
`rapier-markdown-kit/render` exports `createRenderer`, `createMarkdownRenderer`,
|
|
@@ -42,7 +59,7 @@ import {createRenderer} from 'rapier-markdown-kit/render';
|
|
|
42
59
|
// host is your bound DOM/codec/Markdown/image/style environment. Its named ports are
|
|
43
60
|
// declared at the top of createRenderer; preparation returns a detached semantic root.
|
|
44
61
|
const writer = createRenderer(host);
|
|
45
|
-
const {html, filename} = await writer.render(source, {filename: '
|
|
62
|
+
const {html, filename} = await writer.render(source, {filename: 'report.md'});
|
|
46
63
|
```
|
|
47
64
|
|
|
48
65
|
`createMarkdownRenderer(host).render(source, metadata)` returns the semantic HTML.
|
|
@@ -55,9 +72,10 @@ DOM objects between document realms.
|
|
|
55
72
|
This **DOM-hosted library** requires a DOM and sanitizer, the Rapier grammar,
|
|
56
73
|
portable image preparation, style access and the named codec/layout ports it uses.
|
|
57
74
|
Those dependencies keep their own licences: the MIT grant here does not relicense a
|
|
58
|
-
separately supplied host or vendor. The
|
|
59
|
-
|
|
60
|
-
Word exporter and filesystem service are AGPL,
|
|
75
|
+
separately supplied host or vendor. The [editor host binding](https://github.com/jackskip22/rapier/blob/main/src/editor/render-host.js)
|
|
76
|
+
lists the renderer's dependencies. [rapier-server](https://github.com/jackskip22/rapier/blob/main/server/README.md)
|
|
77
|
+
provides a complete local host with a pinned Chromium process. Its Word exporter and filesystem service are AGPL,
|
|
78
|
+
not part of this MIT package.
|
|
61
79
|
|
|
62
80
|
Serialization tests preserve source bytes through HTML export and recovery. Server integration
|
|
63
81
|
tests also check the exported HTTP response through a browser DOM.
|
|
@@ -272,8 +290,9 @@ tolerance.
|
|
|
272
290
|
|
|
273
291
|
## Licence
|
|
274
292
|
|
|
275
|
-
MIT, the full text in `LICENSE`;
|
|
276
|
-
pinned source inventory are in `dist/agent/vendor/pretext/`. The
|
|
293
|
+
MIT, the full text in `LICENSE`; Rapier-owned modules and the stylesheet keep their MIT line. Pretext's licence, notice and
|
|
294
|
+
pinned source inventory are in `dist/agent/vendor/pretext/`. The history projection's jsdiff dependency is BSD-3-Clause;
|
|
295
|
+
`dist/agent/diff.mjs` carries its complete upstream copyright, conditions and disclaimer. The Rapier editor is AGPL-3.0-only and
|
|
277
296
|
none of it is in this package. The kit carries Rapier's release number, written from the one
|
|
278
297
|
value the editor's release reads.
|
|
279
298
|
|
|
@@ -294,7 +313,7 @@ import {exportLedger, readLedger, authorship, merge} from 'rapier-markdown-kit/r
|
|
|
294
313
|
import {writeDocument, readDocument} from 'rapier-markdown-kit/ledger/carried';
|
|
295
314
|
|
|
296
315
|
const ledger = exportLedger({
|
|
297
|
-
text: 'Hello', records: [], documentAuthority: 'example-document', complete: true,
|
|
316
|
+
text: 'Hello', metadata: {filename: 'Hello.md', docKind: 'markdown'}, records: [], documentAuthority: 'example-document', complete: true,
|
|
298
317
|
});
|
|
299
318
|
const saved = writeDocument('Hello', {ledger});
|
|
300
319
|
const reopened = readDocument(saved);
|
|
@@ -308,23 +327,40 @@ console.assert(together.text === 'Hello');
|
|
|
308
327
|
|
|
309
328
|
## The format
|
|
310
329
|
|
|
311
|
-
`rapier-ledger/
|
|
330
|
+
`rapier-ledger/2` is a plain JSON object:
|
|
312
331
|
|
|
313
332
|
```
|
|
314
|
-
{format, documentAuthority, start: {text, revision, root, sha256}, records,
|
|
315
|
-
head: {revision, root, sha256}, complete, sha256}
|
|
316
|
-
records[]: {transaction, splices: [{pos, removed, inserted}], beforeHash, afterHash}
|
|
333
|
+
{format, documentAuthority, start: {text, revision, root, sha256, metadata}, records,
|
|
334
|
+
head: {revision, root, sha256, metadata}, complete, sha256}
|
|
335
|
+
records[]: {transaction, splices: [{pos, removed, inserted}], beforeHash, afterHash, metadata?, authored?}
|
|
317
336
|
transaction: {id, documentAuthority, baseRevision, revision, actor: {kind, id},
|
|
318
337
|
transport, operation, requestId, sourceTransactionId, affectedBlockIds,
|
|
319
338
|
parent, reverts, reapplies, createdAt}
|
|
320
339
|
```
|
|
321
340
|
|
|
322
|
-
`format` is `rapier-ledger/
|
|
341
|
+
`format` is `rapier-ledger/2`. `start.text` is the exact text before the first
|
|
323
342
|
retained record. Every outgoing record carries its own splices, including Undo and
|
|
324
343
|
Redo. Offsets and ranges count UTF-16 code units; no boundary may cut a surrogate
|
|
325
344
|
pair. Splices execute in their recorded order. UTF-8 checksums preserve CRLF, tabs,
|
|
326
345
|
NUL and non-ASCII text. The canonical text limit is 25 MiB; each record has at most
|
|
327
|
-
64 splices. The existing record validator remains authoritative for field bounds.
|
|
346
|
+
64 splices. Metadata is {filename, docKind}; a record may carry filename or docKind pairs {before, after}. Metadata-only acts keep equal source roots and an empty splice list. Inverses reference retained act IDs, so later metadata choices survive selective Undo. File handles and save permissions are never portable. The existing record validator remains authoritative for field bounds.
|
|
347
|
+
|
|
348
|
+
An optional `authored: {basis, source, splices}` preserves an act's original placement
|
|
349
|
+
when concurrent transport changes its physical `record.splices`. `basis` is the sorted,
|
|
350
|
+
minimal frontier of canonical act IDs observed at authoring; its causal closure is
|
|
351
|
+
reconstructed from these same records, including metadata-only acts and inverse
|
|
352
|
+
dependencies. `source` is the SHA-256 of the exact UTF-8 source before the original
|
|
353
|
+
sequential `splices`. An ordinary untransported record omits this field; its retained
|
|
354
|
+
prefix supplies the same evidence. Transport preserves the evidence and the original
|
|
355
|
+
act ID, author, time, turn, label, metadata choices and inverse targets. It neither
|
|
356
|
+
copies the growing prefix into each record nor creates another journal.
|
|
357
|
+
|
|
358
|
+
Use `readLedger` or `exportLedger` to validate retained history and `merge` or
|
|
359
|
+
`transposeAuthored` to transport it. Validation proves the causal closure, source
|
|
360
|
+
hash and exact original-to-physical placement; field shape or equal final text alone
|
|
361
|
+
is insufficient. Do not attach new authored evidence to an arbitrary physical copy.
|
|
362
|
+
Selective Undo uses this evidence to preserve concurrent insertions at their original
|
|
363
|
+
gaps, even when a replacement split into several physical splices.
|
|
328
364
|
|
|
329
365
|
`start.sha256` and `head.sha256` hash UTF-8 text. The envelope `sha256` hashes
|
|
330
366
|
canonical JSON of every other envelope field: recursively sorted object keys,
|
|
@@ -340,16 +376,17 @@ actual document. `historyEnvelope` additionally proves the editor's Undo/Redo br
|
|
|
340
376
|
a fresh hash of the whole current text. SHA-256 of the exact checkpoint text is
|
|
341
377
|
separate. A nonzero start names a retained checkpoint; it does not
|
|
342
378
|
claim to reconstruct discarded events. `complete: false` also permits a stated
|
|
343
|
-
revision gap
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
`actor.kind` is `human`, `agent` or `system`.
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
379
|
+
revision gap in a partial ledger; each retained splice must still replay exactly. An incomplete ledger never
|
|
380
|
+
claims to reconstruct events it does not contain.
|
|
381
|
+
|
|
382
|
+
`actor.kind` is `human`, `agent` or `system`. Canonical doors mint an opaque provenance
|
|
383
|
+
`actor.id` independent of the private principal and retain a supplied display name
|
|
384
|
+
separately as `actor.name` (host names at most 96 characters). The private principal-to-author
|
|
385
|
+
mapping stays in the existing private owner; neither raw nor hashed access credentials
|
|
386
|
+
belong in portable authorship. The name is a **host-supplied claim**, not a verified identity. Timestamps are supplied record times, not independently attested.
|
|
350
387
|
Checksums detect inconsistency/corruption, **not forgery**: anyone able to rewrite a
|
|
351
388
|
file can rewrite its checksums and labels. Importing a label never grants a live
|
|
352
|
-
principal's read handles
|
|
389
|
+
principal's read handles or authenticated document access.
|
|
353
390
|
|
|
354
391
|
## Optional carried parts
|
|
355
392
|
|
|
@@ -359,7 +396,7 @@ beside the document's existing carried source:
|
|
|
359
396
|
| Element | Type and contents | Choice |
|
|
360
397
|
| --- | --- | --- |
|
|
361
398
|
| `#rapier-authorship` | `application/json`, `rapier-authorship/1` | Who wrote what |
|
|
362
|
-
| `#rapier-ledger` | `application/json`, `rapier-ledger/
|
|
399
|
+
| `#rapier-ledger` | `application/json`, `rapier-ledger/2` | The whole history |
|
|
363
400
|
|
|
364
401
|
Each body uses the same reversible `encodeCarried`/`decodeCarried` escape as the
|
|
365
402
|
page's source. `writeParts`, `readParts` and `takeParts` implement it. Duplicate,
|
|
@@ -392,10 +429,10 @@ leading BOM as file metadata. Other Markdown readers ignore the comment. Plain
|
|
|
392
429
|
saves without a choice are unchanged byte for byte. For executable code, use a web
|
|
393
430
|
page to carry metadata rather than appending a Markdown comment to program source.
|
|
394
431
|
|
|
395
|
-
The
|
|
396
|
-
`data-revision`, `data-sha256`, `data-by` and `data-at`, carries
|
|
397
|
-
|
|
398
|
-
|
|
432
|
+
The comparison page's `#rapier-base` is independent: `text/markdown`, with
|
|
433
|
+
`data-name`, `data-revision`, `data-sha256`, `data-by` and `data-at`, carries an exact
|
|
434
|
+
display reference. It is not reconstructed from the optional ledger and never
|
|
435
|
+
replaces current source. `rapier-html --compare original.md revised.md` packages both texts.
|
|
399
436
|
|
|
400
437
|
## Merging copies without a server
|
|
401
438
|
|
|
@@ -414,22 +451,19 @@ unchanged. `review.required` names an unresolved result; a UI must not auto-acce
|
|
|
414
451
|
it. Rapier offers carried-copy imports from Compare through its existing review,
|
|
415
452
|
checks the live document again at commit, and installs the proven ledger before
|
|
416
453
|
publishing its receipt or opening the durable-save barrier. A rejected or stale
|
|
417
|
-
|
|
454
|
+
copy import mutates nothing. A failed install rolls the source and undo window back.
|
|
418
455
|
Copies without a proven common checkpoint are refused by this API.
|
|
419
456
|
|
|
420
457
|
## Host integration and retention
|
|
421
458
|
|
|
422
459
|
The embed helper accepts `agentName`; the checked `rapier-connect` message carries
|
|
423
460
|
`agent: {name}`. WebMCP uses the registration owner's supplied client name. Modern
|
|
424
|
-
MCP uses the request's client metadata, outside tool arguments; a
|
|
425
|
-
request without a supplied name stays unnamed. MCP editor snapshots include the
|
|
461
|
+
MCP uses the request's client metadata, outside tool arguments; a request without a supplied name stays unnamed. MCP editor snapshots include the
|
|
426
462
|
server's retained journal only in editor metadata, not in model-facing tool results.
|
|
427
463
|
The browser proves a replay suffix before preserving remote writers under local
|
|
428
464
|
revision links; missing remote events never become an invented complete history.
|
|
429
465
|
|
|
430
|
-
Rapier
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
preference. Authorship is also opt-in. Neither option implies signatures, identity
|
|
435
|
-
verification, infinite retention, a relay server or automatic synchronization.
|
|
466
|
+
Rapier's live history supports before previews and Undo for individual acts or turns, preserving later edits.
|
|
467
|
+
A carried ledger preserves the history included in that file and explicitly identifies an incomplete start.
|
|
468
|
+
Full history includes removed text and can be much larger than the current document; choose it separately for
|
|
469
|
+
each outgoing file. Authorship is also optional. Neither part verifies identities or synchronizes copies.
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/*! jsdiff 9.0.0 — derivative bundle. Upstream licence, reproduced complete:
|
|
2
|
+
https://github.com/kpdecker/jsdiff/blob/v9.0.0/LICENSE
|
|
3
|
+
|
|
4
|
+
BSD 3-Clause License
|
|
5
|
+
|
|
6
|
+
Copyright (c) 2009-2015, Kevin Decker <kpdecker@gmail.com>
|
|
7
|
+
All rights reserved.
|
|
8
|
+
|
|
9
|
+
Redistribution and use in source and binary forms, with or without
|
|
10
|
+
modification, are permitted provided that the following conditions are met:
|
|
11
|
+
|
|
12
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
13
|
+
list of conditions and the following disclaimer.
|
|
14
|
+
|
|
15
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
16
|
+
this list of conditions and the following disclaimer in the documentation
|
|
17
|
+
and/or other materials provided with the distribution.
|
|
18
|
+
|
|
19
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
20
|
+
contributors may be used to endorse or promote products derived from
|
|
21
|
+
this software without specific prior written permission.
|
|
22
|
+
|
|
23
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
24
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
25
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
26
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
27
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
28
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
29
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
30
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
31
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
32
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
33
|
+
*/
|
|
34
|
+
/* Rapier note on this derivative bundle: the wall clock and the scheduler are injected.
|
|
35
|
+
`diffWithOptionsObj` reads `options.now()` instead of `Date.now()` and `options.schedule(fn,
|
|
36
|
+
ms)` instead of `setTimeout(fn, ms)`. Callers that want a real-time `timeout` or the async
|
|
37
|
+
callback path pass `{ now: Date.now, schedule: setTimeout }` (the Compare Worker in
|
|
38
|
+
editor/engine.js); callers that want a deterministic result bound only by `maxEditLength` pass
|
|
39
|
+
neither (the kernel), and the diff then depends on nothing but its two input strings. This keeps
|
|
40
|
+
one diff factory for the whole app and keeps agent/kernel.mjs inside the decision-purity gate
|
|
41
|
+
(tools/purity-gate.mjs) without any per-file exemption. */
|
|
42
|
+
export function createDiff(){var O=Object.defineProperty;var Y=Object.getOwnPropertyDescriptor;var B=Object.getOwnPropertyNames;var Q=Object.prototype.hasOwnProperty;var X=(i,e)=>{for(var n in e)O(i,n,{get:e[n],enumerable:!0})},Z=(i,e,n,t)=>{if(e&&typeof e=="object"||typeof e=="function")for(let l of B(e))!Q.call(i,l)&&l!==n&&O(i,l,{get:()=>e[l],enumerable:!(t=Y(e,l))||t.enumerable});return i};var K=i=>Z(O({},"__esModule",{value:!0}),i);var oe={};X(oe,{diffChars:()=>diffChars,diffLines:()=>y,diffWordsWithSpace:()=>z,structuredPatch:()=>U});var C=class{diff(e,n,t={}){let l;typeof t=="function"?(l=t,t={}):"callback"in t&&(l=t.callback);let o=this.castInput(e,t),r=this.castInput(n,t),u=this.removeEmpty(this.tokenize(o,t)),a=this.removeEmpty(this.tokenize(r,t));return this.diffWithOptionsObj(u,a,t,l)}diffWithOptionsObj(e,n,t,l){var o;let r=s=>{if(s=this.postProcess(s,t),l){t.schedule(function(){l(s)},0);return}else return s},u=n.length,a=e.length,f=1,c=u+a;t.maxEditLength!=null&&(c=Math.min(c,t.maxEditLength));let w=(o=t.timeout)!==null&&o!==void 0?o:1/0,p=(t.now?t.now():0)+w,m=[{oldPos:-1,lastComponent:void 0}],x=this.extractCommon(m[0],n,e,0,t);if(m[0].oldPos+1>=a&&x+1>=u)return r(this.buildValues(m[0].lastComponent,n,e));let v=-1/0,P=1/0,E=()=>{for(let s=Math.max(v,-f);s<=Math.min(P,f);s+=2){let d,h=m[s-1],g=m[s+1];h&&(m[s-1]=void 0);let F=!1;if(g){let q=g.oldPos-s;F=g&&0<=q&&q<u}let D=h&&h.oldPos+1<a;if(!F&&!D){m[s]=void 0;continue}if(!D||F&&h.oldPos<g.oldPos?d=this.addToPath(g,!0,!1,0,t):d=this.addToPath(h,!1,!0,1,t),x=this.extractCommon(d,n,e,s,t),d.oldPos+1>=a&&x+1>=u)return r(this.buildValues(d.lastComponent,n,e))||!0;m[s]=d,d.oldPos+1>=a&&(P=Math.min(P,s-1)),x+1>=u&&(v=Math.max(v,s+1))}f++};if(l)(function s(){t.schedule(function(){if(f>c||(t.now?t.now():0)>p)return l(void 0);E()||s()},0)})();else for(;f<=c&&(t.now?t.now():0)<=p;){let s=E();if(s)return s}}addToPath(e,n,t,l,o){let r=e.lastComponent;return r&&!o.oneChangePerToken&&r.added===n&&r.removed===t?{oldPos:e.oldPos+l,lastComponent:{count:r.count+1,added:n,removed:t,previousComponent:r.previousComponent}}:{oldPos:e.oldPos+l,lastComponent:{count:1,added:n,removed:t,previousComponent:r}}}extractCommon(e,n,t,l,o){let r=n.length,u=t.length,a=e.oldPos,f=a-l,c=0;for(;f+1<r&&a+1<u&&this.equals(t[a+1],n[f+1],o);)f++,a++,c++,o.oneChangePerToken&&(e.lastComponent={count:1,previousComponent:e.lastComponent,added:!1,removed:!1});return c&&!o.oneChangePerToken&&(e.lastComponent={count:c,previousComponent:e.lastComponent,added:!1,removed:!1}),e.oldPos=a,f}equals(e,n,t){return t.comparator?t.comparator(e,n):e===n||!!t.ignoreCase&&e.toLowerCase()===n.toLowerCase()}removeEmpty(e){let n=[];for(let t=0;t<e.length;t++)e[t]&&n.push(e[t]);return n}castInput(e,n){return e}tokenize(e,n){return Array.from(e)}join(e){return e.join("")}postProcess(e,n){return e}buildValues(e,n,t){let l=[],o;for(;e;)l.push(e),o=e.previousComponent,delete e.previousComponent,e=o;l.reverse();let r=l.length,u=0,a=0,f=0;for(;u<r;u++){let c=l[u];if(c.removed)c.value=this.join(t.slice(f,f+c.count)),f+=c.count;else{c.value=this.join(n.slice(a,a+c.count));a+=c.count,c.added||(f+=c.count)}}return l}};var I="a-zA-Z0-9_\\u{AD}\\u{C0}-\\u{D6}\\u{D8}-\\u{F6}\\u{F8}-\\u{2C6}\\u{2C8}-\\u{2D7}\\u{2DE}-\\u{2FF}\\u{1E00}-\\u{1EFF}";var R=class extends C{tokenize(e){let n=new RegExp(`(\\r?\\n)|[${I}]+|[^\\S\\n\\r]+|[^${I}]`,"ug");return e.match(n)||[]}},J=new R;function z(i,e,n){return J.diff(i,e,n)}var $=class extends C{constructor(){super(...arguments),this.tokenize=ie}equals(e,n,t){return t.ignoreWhitespace?((!t.newlineIsToken||!e.includes(`
|
|
43
|
+
`))&&(e=e.trim()),(!t.newlineIsToken||!n.includes(`
|
|
44
|
+
`))&&(n=n.trim())):t.ignoreNewlineAtEof&&!t.newlineIsToken&&(e.endsWith(`
|
|
45
|
+
`)&&(e=e.slice(0,-1)),n.endsWith(`
|
|
46
|
+
`)&&(n=n.slice(0,-1))),super.equals(e,n,t)}},V=new $;function y(i,e,n){return V.diff(i,e,n)}function ie(i,e){e.stripTrailingCr&&(i=i.replace(/\r\n/g,`
|
|
47
|
+
`));let n=[],t=i.split(/(\n|\r\n)/);t[t.length-1]||t.pop();for(let l=0;l<t.length;l++){let o=t[l];l%2&&!e.newlineIsToken?n[n.length-1]+=o:n.push(o)}return n}function U(i,e,n,t,l,o,r){let u;r?typeof r=="function"?u={callback:r}:u=r:u={},typeof u.context>"u"&&(u.context=4);let a=u.context;if(u.newlineIsToken)throw new Error("newlineIsToken may not be used with patch-generation functions, only with diffing functions");if(u.callback){let{callback:c}=u;y(n,t,Object.assign(Object.assign({},u),{callback:w=>{let p=f(w);c(p)}}))}else return f(y(n,t,u));function f(c){if(!c)return;c.push({value:"",lines:[]});function w(s){return s.map(function(d){return" "+d})}let p=[],m=0,x=0,v=[],P=1,E=1;for(let s=0;s<c.length;s++){let d=c[s],h=d.lines||le(d.value);if(d.lines=h,d.added||d.removed){if(!m){let g=c[s-1];m=P,x=E,g&&(v=a>0?w(g.lines.slice(-a)):[],m-=v.length,x-=v.length)}for(let g of h)v.push((d.added?"+":"-")+g);d.added?E+=h.length:P+=h.length}else{if(m)if(h.length<=a*2&&s<c.length-2)for(let g of w(h))v.push(g);else{let g=Math.min(h.length,a);for(let D of w(h.slice(0,g)))v.push(D);let F={oldStart:m,oldLines:P-m+g,newStart:x,newLines:E-x+g,lines:v};p.push(F),m=0,x=0,v=[]}P+=h.length,E+=h.length}}for(let s of p)for(let d=0;d<s.lines.length;d++)s.lines[d].endsWith(`
|
|
48
|
+
`)?s.lines[d]=s.lines[d].slice(0,-1):(s.lines.splice(d+1,0,"\"),d++);return{oldFileName:i,newFileName:e,oldHeader:l,newHeader:o,hunks:p}}}function le(i){let e=i.endsWith(`
|
|
49
|
+
`),n=i.split(`
|
|
50
|
+
`).map(t=>t+`
|
|
51
|
+
`);return e?n.pop():n.push(n.pop().slice(0,-1)),n}function diffChars(before,after,options){return new C().diff(before,after,options)}return K(oe);}
|
|
52
|
+
export const {diffChars, diffLines, diffWordsWithSpace, structuredPatch} = createDiff();
|
|
@@ -33,7 +33,7 @@ export function readAuthorship(input, text) {
|
|
|
33
33
|
let cursor = 0;
|
|
34
34
|
for (const run of value.runs) {
|
|
35
35
|
if (!Array.isArray(run) || run.length !== 5 || run[0] !== cursor || !nonnegative(run[1]) || run[1] <= cursor || run[1] > text.length ||
|
|
36
|
-
!['human','agent','system'].includes(run[2]) || typeof run[3] !== 'string' || !run[3] || run[3].length > 160 || !nonnegative(run[4]) ||
|
|
36
|
+
!['human','agent','system'].includes(run[2]) || typeof run[3] !== 'string' || !run[3] || run[3].length > 160 || !(run[4] === null || nonnegative(run[4])) ||
|
|
37
37
|
/[\uD800-\uDFFF]/u.test(text.slice(cursor, run[1]))) throw fault('invalid authorship run');
|
|
38
38
|
cursor = run[1];
|
|
39
39
|
}
|
|
@@ -41,7 +41,7 @@ export function readAuthorship(input, text) {
|
|
|
41
41
|
return value;
|
|
42
42
|
}
|
|
43
43
|
// Authorship-only imports become an incomplete insertion ledger. There is no mutable blame database.
|
|
44
|
-
export function ledgerFromAuthorship(input, text, documentAuthority) {
|
|
44
|
+
export function ledgerFromAuthorship(input, text, documentAuthority, metadata) {
|
|
45
45
|
const value = readAuthorship(input, text), records = [];
|
|
46
46
|
let current = '', root = textRoot('');
|
|
47
47
|
for (const [start, end, kind, id, createdAt] of value.runs) {
|
|
@@ -55,5 +55,5 @@ export function ledgerFromAuthorship(input, text, documentAuthority) {
|
|
|
55
55
|
reverts: null, reapplies: null, createdAt,
|
|
56
56
|
}});
|
|
57
57
|
}
|
|
58
|
-
return exportLedger({text: current, records, documentAuthority, revision: records.length, root, complete: false});
|
|
58
|
+
return exportLedger({text: current, metadata, records, documentAuthority, revision: records.length, root, complete: false});
|
|
59
59
|
}
|
|
@@ -9,10 +9,10 @@ export function encodeCarried(text) {
|
|
|
9
9
|
export function decodeCarried(text) {
|
|
10
10
|
return String(text).replace(/\\([\\/!r0])/g, (_, c) => c === '\\' ? '\\' : c === 'r' ? '\r' : c === '0' ? '\0' : c === '/' ? '</' : '<!');
|
|
11
11
|
}
|
|
12
|
-
export function validateParts(text, {ledger = null, authorship = null} = {}) {
|
|
12
|
+
export function validateParts(text, {ledger = null, authorship = null, metadata} = {}) {
|
|
13
13
|
// A file's leading BOM is metadata, as in the editor's source intake.
|
|
14
14
|
const canonical = text.charCodeAt(0) === 0xfeff ? text.slice(1) : text;
|
|
15
|
-
const history = ledger === null ? null : readLedger(ledger, canonical).ledger;
|
|
15
|
+
const history = ledger === null ? null : readLedger(ledger, canonical, metadata).ledger;
|
|
16
16
|
const runs = authorship === null ? null : readAuthorship(authorship, canonical);
|
|
17
17
|
if (history && runs && canonicalJSON(projectAuthorship(history)) !== canonicalJSON(runs)) throw fault('authorship differs from ledger replay');
|
|
18
18
|
return {ledger: history, authorship: runs};
|
|
@@ -69,19 +69,19 @@ export function readDocument(source) {
|
|
|
69
69
|
return {text, ...validateParts(text, parts)};
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
-
// One portable baseline. A digest is integrity evidence, not authentication of the named
|
|
72
|
+
// One portable baseline. A digest is integrity evidence, not authentication of the named author.
|
|
73
73
|
export function readBase(value) {
|
|
74
74
|
if (!value || typeof value !== 'object' || typeof value.text !== 'string' ||
|
|
75
75
|
typeof value.sha256 !== 'string' || !/^[a-f0-9]{64}$/.test(value.sha256) || sha256(value.text) !== value.sha256)
|
|
76
|
-
throw fault('
|
|
76
|
+
throw fault('comparison base SHA-256 differs from its text');
|
|
77
77
|
if (typeof value.revision !== 'string' || !value.revision || value.revision.length > 256 || /[\u0000-\u001f\u007f]/.test(value.revision))
|
|
78
|
-
throw fault('invalid
|
|
78
|
+
throw fault('invalid comparison base revision');
|
|
79
79
|
if (typeof value.name !== 'string' || !value.name || [...value.name].length > 256 || /[\\/\u0000-\u001f\u007f]/.test(value.name))
|
|
80
|
-
throw fault('invalid
|
|
80
|
+
throw fault('invalid comparison base name');
|
|
81
81
|
if (typeof value.by !== 'string' || !value.by.trim() || value.by.length > 96 || /[\u0000-\u001f\u007f]/.test(value.by))
|
|
82
|
-
throw fault('a
|
|
82
|
+
throw fault('a comparison needs its author name');
|
|
83
83
|
if (typeof value.at !== 'string' || !/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,3})?Z$/.test(value.at) || !Number.isFinite(Date.parse(value.at)))
|
|
84
|
-
throw fault('the time of a
|
|
84
|
+
throw fault('the time of a comparison is an ISO 8601 UTC timestamp');
|
|
85
85
|
return {text: value.text, sha256: value.sha256, revision: value.revision, name: value.name, by: value.by, at: value.at};
|
|
86
86
|
}
|
|
87
87
|
const escapeAttribute = value => value.replace(/&/g, '&').replace(/"/g, '"').replace(/</g, '<').replace(/>/g, '>');
|
|
@@ -98,7 +98,7 @@ export function readBaseElements(elements) {
|
|
|
98
98
|
if (!rows.length) return null;
|
|
99
99
|
const row = rows[0];
|
|
100
100
|
if (rows.length !== 1 || row.type !== 'text/markdown' || (row.tagName && row.tagName.toLowerCase() !== 'script'))
|
|
101
|
-
throw fault('duplicate or executable
|
|
101
|
+
throw fault('duplicate or executable comparison base');
|
|
102
102
|
const get = name => row.getAttribute ? row.getAttribute('data-' + name) : row[name];
|
|
103
103
|
return readBase({text: decodeCarried(row.textContent), ...Object.fromEntries(['name', 'sha256', 'revision', 'by', 'at'].map(key => [key, get(key)]))});
|
|
104
104
|
}
|
|
@@ -109,7 +109,7 @@ export function takeBase(html) {
|
|
|
109
109
|
if (!pairs.some(([key, value]) => key === 'id' && value === 'rapier-base')) return whole;
|
|
110
110
|
const seen = new Set(), values = {};
|
|
111
111
|
for (const [key, value] of pairs) {
|
|
112
|
-
if (seen.has(key)) throw fault('ambiguous
|
|
112
|
+
if (seen.has(key)) throw fault('ambiguous comparison base');
|
|
113
113
|
seen.add(key); values[key] = value;
|
|
114
114
|
}
|
|
115
115
|
elements.push({id: values.id, type: values.type, textContent: content,
|
|
@@ -1,24 +1,26 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
|
-
import {_rapierValidLedgerRecord, _rapierRecordSplices, _rapierTransformSplices} from './journal-records.mjs';
|
|
2
|
+
import {_rapierValidLedgerRecord, _rapierRecordSplices, _rapierTransformSplices, _rapierRecordMetadata, _rapierValidMetadata, _rapierTransformMetadata, _rapierReplayMetadata} from './journal-records.mjs';
|
|
3
|
+
import {authoredHistory} from './transport.mjs';
|
|
3
4
|
import {textRoot, rootAfter, sha256} from './hash.mjs';
|
|
4
5
|
import {canonicalJSON, plainData, documentText, exactKeys, nonnegative, fault} from './data.mjs';
|
|
5
6
|
export {textRoot, rootAfter};
|
|
6
7
|
const digest = value => sha256(canonicalJSON(value));
|
|
7
8
|
const rootShape = value => typeof value === 'string' && /^\d+:[a-z0-9]+:[a-z0-9]+$/.test(value);
|
|
8
|
-
export function readLedger(input, expectedText) {
|
|
9
|
+
export function readLedger(input, expectedText, expectedMetadata) {
|
|
9
10
|
const ledger = plainData(input);
|
|
10
|
-
if (!exactKeys(ledger, ['format','documentAuthority','start','records','head','complete','sha256']) || ledger.format !== 'rapier-ledger/
|
|
11
|
+
if (!exactKeys(ledger, ['format','documentAuthority','start','records','head','complete','sha256']) || ledger.format !== 'rapier-ledger/2' ||
|
|
11
12
|
typeof ledger.documentAuthority !== 'string' || !ledger.documentAuthority || ledger.documentAuthority.length > 256 ||
|
|
12
13
|
typeof ledger.complete !== 'boolean' || !Array.isArray(ledger.records) ||
|
|
13
|
-
!exactKeys(ledger.start, ['text','revision','root','sha256']) || !exactKeys(ledger.head, ['revision','root','sha256'])) throw fault('invalid envelope');
|
|
14
|
+
!exactKeys(ledger.start, ['text','revision','root','sha256','metadata']) || !exactKeys(ledger.head, ['revision','root','sha256','metadata'])) throw fault('unsupported or invalid envelope');
|
|
14
15
|
const {sha256: claimed, ...payload} = ledger;
|
|
15
16
|
if (claimed !== digest(payload)) throw fault('envelope checksum differs');
|
|
16
17
|
let text = documentText(ledger.start.text), root = ledger.start.root, revision = ledger.start.revision;
|
|
18
|
+
if (!_rapierValidMetadata(ledger.start.metadata) || !_rapierValidMetadata(ledger.head.metadata)) throw fault('invalid document metadata');
|
|
17
19
|
if (!nonnegative(revision) || !rootShape(root) || (revision === 0 && root !== textRoot(text)) || ledger.start.sha256 !== sha256(text) ||
|
|
18
20
|
(ledger.complete && revision !== 0)) throw fault('invalid start');
|
|
19
21
|
const earlier = [];
|
|
20
22
|
for (const record of ledger.records) {
|
|
21
|
-
if (!Array.isArray(record.splices) || !
|
|
23
|
+
if (!Array.isArray(record.splices) || !_rapierValidLedgerRecord(record, earlier.at(-1) || null, earlier,
|
|
22
24
|
ledger.documentAuthority, 25 * 1024 * 1024, text => new TextEncoder().encode(text).length, !ledger.complete) ||
|
|
23
25
|
record.transaction.baseRevision < revision || (record.transaction.baseRevision !== revision && ledger.complete) ||
|
|
24
26
|
(record.transaction.baseRevision === revision && record.beforeHash !== root)) throw fault('record chain differs');
|
|
@@ -31,39 +33,50 @@ export function readLedger(input, expectedText) {
|
|
|
31
33
|
if (root !== record.afterHash) throw fault('record root differs');
|
|
32
34
|
revision = record.transaction.revision; earlier.push(record);
|
|
33
35
|
}
|
|
36
|
+
if (ledger.records.some(row => Object.hasOwn(row, 'authored'))) {
|
|
37
|
+
try { authoredHistory(ledger.start.text, ledger.records); } catch (_) { throw fault('authored origin differs'); }
|
|
38
|
+
}
|
|
39
|
+
const metadata = _rapierReplayMetadata(ledger.start.metadata, ledger.records);
|
|
40
|
+
if (!metadata || canonicalJSON(metadata) !== canonicalJSON(ledger.head.metadata) ||
|
|
41
|
+
(expectedMetadata !== undefined && (!_rapierValidMetadata(expectedMetadata) || canonicalJSON(metadata) !== canonicalJSON(expectedMetadata))))
|
|
42
|
+
throw fault('metadata does not describe this document');
|
|
34
43
|
if (ledger.head.revision !== revision || ledger.head.root !== root || ledger.head.sha256 !== sha256(text) ||
|
|
35
44
|
(expectedText !== undefined && text !== expectedText)) throw fault('history does not describe this document');
|
|
36
|
-
return {ledger, text, root, revision, complete: ledger.complete};
|
|
45
|
+
return {ledger, text, metadata, root, revision, complete: ledger.complete};
|
|
37
46
|
}
|
|
38
|
-
export function exportLedger({text, records, documentAuthority, revision, root, complete = false, earlier = records} = {}) {
|
|
47
|
+
export function exportLedger({text, metadata, records, documentAuthority, revision, root, complete = false, earlier = records} = {}) {
|
|
39
48
|
documentText(text);
|
|
49
|
+
if (!_rapierValidMetadata(metadata)) throw fault('document metadata required');
|
|
40
50
|
if (!Array.isArray(records) || !Array.isArray(earlier)) throw fault('records required');
|
|
41
51
|
// Inspect before touching properties: exporting must not call accessors on an alleged record.
|
|
42
52
|
records = plainData(records); earlier = plainData(earlier);
|
|
43
53
|
const expanded = records.map(record => {
|
|
44
|
-
const rows = _rapierRecordSplices(record, earlier);
|
|
45
|
-
if (!rows) throw fault('navigation target was not retained');
|
|
54
|
+
const rows = _rapierRecordSplices(record, earlier), effect = _rapierRecordMetadata(record, earlier);
|
|
55
|
+
if (!rows || effect === undefined) throw fault('navigation target was not retained');
|
|
46
56
|
return {transaction: record.transaction, splices: rows.map(row => ({pos: row.pos, removed: row.removed, inserted: row.inserted})),
|
|
47
|
-
beforeHash: record.beforeHash, afterHash: record.afterHash}
|
|
57
|
+
beforeHash: record.beforeHash, afterHash: record.afterHash, ...(Object.hasOwn(record, 'authored') ? {authored: record.authored} : {}), ...(effect ? {metadata: effect} : {}),
|
|
58
|
+
...(record.changeSet && Object.hasOwn(record.changeSet, 'label') ? {changeSet: {label: record.changeSet.label}} : {}),
|
|
59
|
+
...(record.derivedCommentIndex == null ? {} : {derivedCommentIndex: record.derivedCommentIndex})};
|
|
48
60
|
});
|
|
49
|
-
let startText = text;
|
|
61
|
+
let startText = text, startMetadata = {...metadata};
|
|
50
62
|
for (let i = expanded.length - 1; i >= 0; i--) {
|
|
51
63
|
startText = _rapierTransformSplices(startText, expanded[i].splices, true);
|
|
52
|
-
|
|
64
|
+
startMetadata = _rapierTransformMetadata(startMetadata, expanded[i].metadata, true);
|
|
65
|
+
if (startText === null || !startMetadata) throw fault('history does not reverse from this document');
|
|
53
66
|
}
|
|
54
67
|
revision ??= expanded.at(-1)?.transaction.revision ?? 0;
|
|
55
68
|
root ??= expanded.at(-1)?.afterHash ?? textRoot(text);
|
|
56
69
|
documentAuthority ??= expanded[0]?.transaction.documentAuthority;
|
|
57
70
|
const startRevision = expanded[0]?.transaction.baseRevision ?? revision;
|
|
58
|
-
const payload = {format: 'rapier-ledger/
|
|
59
|
-
start: {text: startText, revision: startRevision, root: expanded[0]?.beforeHash ?? root, sha256: sha256(startText)},
|
|
60
|
-
records: expanded, head: {revision, root, sha256: sha256(text)}, complete: complete === true && startRevision === 0 && expanded.every((row, i) => !i || row.transaction.baseRevision === expanded[i - 1].transaction.revision)};
|
|
71
|
+
const payload = {format: 'rapier-ledger/2', documentAuthority,
|
|
72
|
+
start: {text: startText, revision: startRevision, root: expanded[0]?.beforeHash ?? root, sha256: sha256(startText), metadata: startMetadata},
|
|
73
|
+
records: expanded, head: {revision, root, sha256: sha256(text), metadata: {...metadata}}, complete: complete === true && startRevision === 0 && expanded.every((row, i) => !i || row.transaction.baseRevision === expanded[i - 1].transaction.revision)};
|
|
61
74
|
const ledger = {...payload, sha256: digest(payload)};
|
|
62
|
-
readLedger(ledger, text); return ledger;
|
|
75
|
+
readLedger(ledger, text, metadata); return ledger;
|
|
63
76
|
}
|
|
64
77
|
// Derived navigation, never a second history. A target older than start is intentionally unavailable.
|
|
65
78
|
export function historyEnvelope(input, segmentIdentity = []) {
|
|
66
|
-
const {ledger, text} = readLedger(input), ids = new Set(ledger.records.map(row => row.transaction.id));
|
|
79
|
+
const {ledger, text, metadata} = readLedger(input), ids = new Set(ledger.records.map(row => row.transaction.id));
|
|
67
80
|
let branch = [], cursor = 0;
|
|
68
81
|
for (const {transaction: tx} of ledger.records) {
|
|
69
82
|
const target = tx.reverts || tx.reapplies;
|
|
@@ -75,28 +88,31 @@ export function historyEnvelope(input, segmentIdentity = []) {
|
|
|
75
88
|
} else { branch = branch.slice(0, cursor); branch.push(tx.id); cursor = branch.length; }
|
|
76
89
|
}
|
|
77
90
|
const byId = new Map(ledger.records.map(row => [row.transaction.id, row]));
|
|
78
|
-
let traversed = text;
|
|
91
|
+
let traversed = text, traversedMetadata = {...metadata};
|
|
79
92
|
for (let i = cursor - 1; i >= 0; i--) {
|
|
80
93
|
traversed = _rapierTransformSplices(traversed, byId.get(branch[i]).splices, true);
|
|
81
|
-
|
|
94
|
+
traversedMetadata = _rapierTransformMetadata(traversedMetadata, byId.get(branch[i]).metadata, true);
|
|
95
|
+
if (traversed === null || !traversedMetadata) throw fault('undo branch does not reverse');
|
|
82
96
|
}
|
|
83
|
-
traversed = text;
|
|
97
|
+
traversed = text; traversedMetadata = {...metadata};
|
|
84
98
|
for (let i = cursor; i < branch.length; i++) {
|
|
85
99
|
traversed = _rapierTransformSplices(traversed, byId.get(branch[i]).splices);
|
|
86
|
-
|
|
100
|
+
traversedMetadata = _rapierTransformMetadata(traversedMetadata, byId.get(branch[i]).metadata);
|
|
101
|
+
if (traversed === null || !traversedMetadata) throw fault('redo branch does not replay');
|
|
87
102
|
}
|
|
88
103
|
if (ledger.complete) {
|
|
89
104
|
if (ledger.records.length && !branch.length) throw fault('complete history lost its branch');
|
|
90
|
-
let active = ledger.start.text;
|
|
91
|
-
if (!cursor && active !== text) throw fault('complete history start differs');
|
|
105
|
+
let active = ledger.start.text, activeMetadata = {...ledger.start.metadata};
|
|
106
|
+
if (!cursor && (active !== text || canonicalJSON(activeMetadata) !== canonicalJSON(metadata))) throw fault('complete history start differs');
|
|
92
107
|
for (let i = 0; i < branch.length; i++) {
|
|
93
108
|
active = _rapierTransformSplices(active, byId.get(branch[i]).splices);
|
|
94
|
-
|
|
109
|
+
activeMetadata = _rapierTransformMetadata(activeMetadata, byId.get(branch[i]).metadata);
|
|
110
|
+
if (active === null || !activeMetadata || (i + 1 === cursor && (active !== text || canonicalJSON(activeMetadata) !== canonicalJSON(metadata)))) throw fault('complete branch differs');
|
|
95
111
|
}
|
|
96
112
|
}
|
|
97
|
-
return {schemaVersion:
|
|
113
|
+
return {schemaVersion: 5, documentAuthority: ledger.documentAuthority, documentRevision: ledger.head.revision,
|
|
98
114
|
ledger: ledger.records, branch, cursor, earliestRevision: ledger.start.revision, earliestHash: ledger.start.root,
|
|
99
|
-
sourceRootId: ledger.head.root, segmentIdentity, historyComplete: ledger.complete,
|
|
115
|
+
sourceRootId: ledger.head.root, earliestMetadata: {...ledger.start.metadata}, documentMetadata: {...metadata}, segmentIdentity, historyComplete: ledger.complete,
|
|
100
116
|
trimReason: ledger.complete ? '' : 'carried_start', trimmedBytes: 0};
|
|
101
117
|
}
|
|
102
118
|
export function agentActorId(door, agent) {
|
|
@@ -124,8 +140,8 @@ export function replaceLedgerText(input, text, {actor = {kind: 'system', id: 'ra
|
|
|
124
140
|
documentAuthority: current.ledger.documentAuthority, baseRevision: current.revision, revision,
|
|
125
141
|
actor, transport: 'platform', operation, requestId: null, sourceTransactionId: null,
|
|
126
142
|
affectedBlockIds: [], parent: current.ledger.records.at(-1)?.transaction.id ?? null,
|
|
127
|
-
reverts: null, reapplies: null, createdAt: at ??
|
|
143
|
+
reverts: null, reapplies: null, createdAt: at ?? null,
|
|
128
144
|
}};
|
|
129
|
-
return exportLedger({text, records: [...current.ledger.records, record], documentAuthority: current.ledger.documentAuthority,
|
|
145
|
+
return exportLedger({text, metadata: current.metadata, records: [...current.ledger.records, record], documentAuthority: current.ledger.documentAuthority,
|
|
130
146
|
revision, root, complete: current.complete});
|
|
131
147
|
}
|