diff options
| author | Ralph Amissah <ralph.amissah@gmail.com> | 2026-09-21 10:52:39 -0400 |
|---|---|---|
| committer | Ralph Amissah <ralph.amissah@gmail.com> | 2026-09-22 14:23:53 -0400 |
| commit | e1c9056903c2478066d5da8934d704f4d1935da4 (patch) | |
| tree | f3fbfee319f3947e216f996d5097480bf82d65cb /src | |
| parent | test (ref-abstr-ssp): live-manual mended headers (diff) | |
abstraction: format 2.0, source.digests
format 2.0, source.digest covers every markup file, the sha256 over one
line per markup file, "<sha256> <filename>", sorted by filename.
source.digest was the sha256 of the master file as read, before any
insert. This is right for a .sst, which is the whole document. For a
.ssm with inserts .ssi (containing the substantive part of a documents
text) this is close to meaningless.
Considered change of meaning (rather than an addition) and given the
major part of the format version with it: 1.1 becomes 2.0, in the .ssp
header line and in the database's schema.version row together.
A 1.x reader refuses a 2.0 artefact, which is what that check is for.
- Sorted, so the value does not depend on the order a filesystem hands
back a directory.
- By filename (rather than by path), so it does not depend on where the
pod sits, which is the property the pod rebuild comparison rests on:
a pod unzipped elsewhere must still give the same abstraction.
Within one language every markup file lives in one directory, so a
filename identifies it.
Each line is checkable on its own against a digests.txt line or a files
row, which the single value was not. The whole is reproducible with
sha256sum and sort, and was verified that way for a one file document
and for a twenty file one.
An unreadable file is named in the digest rather than skipped. The parse
has failed elsewhere by then, and a digest that quietly left a file out
would claim the document is something it is not.
The reference abstractions are regenerated. Two lines change in each and
no others: the version, and the digest.
(assisted by Claude-Code)
Diffstat (limited to 'src')
| -rw-r--r-- | src/sisudoc/ocda/abstraction/ssp.d | 13 | ||||
| -rw-r--r-- | src/sisudoc/ocda/abstraction/ssp_in.d | 2 | ||||
| -rw-r--r-- | src/sisudoc/ocda/io_in/read_source_files.d | 54 |
3 files changed, 65 insertions, 4 deletions
diff --git a/src/sisudoc/ocda/abstraction/ssp.d b/src/sisudoc/ocda/abstraction/ssp.d index 487a33b..c22741b 100644 --- a/src/sisudoc/ocda/abstraction/ssp.d +++ b/src/sisudoc/ocda/abstraction/ssp.d @@ -59,14 +59,23 @@ module sisudoc.ocda.abstraction.ssp; part when a property is added (a reader of the newer format still reads the older one), the major part when one changes meaning or goes away. + 2.0 2026-09-18 source.digest changes meaning: it now covers every + markup file the document is made of, where it was the + sha256 of the master file alone. For a .sst that was + right; for a .ssm it was close to meaningless + (live-manual's digest was 2,143 bytes of master while + the document itself is in 19 inserts, and) any chapter + could be edited without the digest moving. Being a + change of meaning rather than an addition, it takes the + major part with it. 1.1 2026-09-10 @source block; the document metadata the output writers read carried in full (14 meta and 3 make properties added); .segment_lv4_is, the segment a cross reference to a heading above level 4 lands on; was 0.1 +/ -enum ssp_format_version = "1.1"; -enum ssp_format_version_major = "1"; +enum ssp_format_version = "2.0"; +enum ssp_format_version_major = "2"; /+ ↓ escaping for values that are folded onto one line a .ssp property value runs to the end of its line, and diff --git a/src/sisudoc/ocda/abstraction/ssp_in.d b/src/sisudoc/ocda/abstraction/ssp_in.d index a71087a..0e461ad 100644 --- a/src/sisudoc/ocda/abstraction/ssp_in.d +++ b/src/sisudoc/ocda/abstraction/ssp_in.d @@ -76,7 +76,7 @@ template spineAbstractionRead() { object sections in the order the file gives them +/ struct SSPdocument { - string format; // "% SiSU Document Abstraction v1.1" + string format; // "% SiSU Document Abstraction v2.0" string source; // "% Source: ..." string[string] source_info; // the @source block string[] source_info_order; diff --git a/src/sisudoc/ocda/io_in/read_source_files.d b/src/sisudoc/ocda/io_in/read_source_files.d index 4ceb039..1881adc 100644 --- a/src/sisudoc/ocda/io_in/read_source_files.d +++ b/src/sisudoc/ocda/io_in/read_source_files.d @@ -109,6 +109,36 @@ template spineRawMarkupContent() { = rawsrc.sourceContentSplitIntoHeaderAndBody(_opt_action, rawsrc.sourceContent(fn_src), fn_src); return _0_header_1_body_content_2_insert_filelist_struct; } + /+ ↓ one line per markup file, "<sha256> <filename>", sorted, then the + sha256 of that. The imports are local: this template is mixed in + beside others, and sort or join arriving in a mixing scope takes over + a UFCS lookup somewhere else. + +/ + @trusted private ubyte[32] _markupFilesDigest(string[] _files) { + import std.algorithm : sort; + import std.array : join; + import std.conv : to; + import std.digest : toHexString; + import std.file : read; + import std.path : baseName; + string[] _lines; + foreach (_f; _files) { + if (_f.length == 0) { continue; } + try { + _lines ~= (cast(ubyte[]) _f.read).sha256Of.toHexString.to!string + ~ " " ~ _f.baseName; + } catch (Exception) { + /+ ↓ a file the scan named and the filesystem will not give. The + parse has failed elsewhere already, and a digest that quietly + left it out would claim the document is something it is not, so + it is named instead. + +/ + _lines ~= "UNREADABLE " ~ _f.baseName; + } + } + _lines.sort; + return _lines.join("\n").sha256Of; + } struct RawMarkupContent { final sourceContent(in string fn_src) { auto raw = MarkupRawUnit(); @@ -144,7 +174,29 @@ template spineRawMarkupContent() { string header_type = ""; ST_doc_digest dig; { - dig.markup_doc = source_txt_str.sha256Of; + /+ ↓ the digest of the document's markup, over every file it is made + of and not over the master (.ssm) alone. + . + It was source_txt_str.sha256Of: the master file as read, before any + insert. For a .sst, which is the whole document, that is right. For + a .ssm it is close to meaningless (live-manual's master is 2,143 + bytes of title and nineteen << lines, while the document is in the + nineteen files those lines name). Any chapter in any language could + be rewritten and the digest would not move. + . + It is now the sha256 over one line per markup file, + "<sha256> <filename>", sorted by filename. Sorted so that the + value does not depend on the order a filesystem hands back a + directory; by filename rather than by path so that it does not + depend on where the pod sits, which is the property the pod + rebuild comparison rests on. Within one language every markup + file lives in one directory, so a filename identifies it. + . + Each line is checkable on its own against a digests.txt line or + a files row, which the old single value was not. + +/ + dig.markup_doc = _markupFilesDigest( + [fn_src.to!string] ~ insert_file_list_get); dig.header = st.header.sha256Of; dig.text = sourcefile_body_content.sha256Of; } |
