aboutsummaryrefslogtreecommitdiffhomepage
path: root/src
diff options
context:
space:
mode:
authorRalph Amissah <ralph.amissah@gmail.com>2026-09-21 10:52:39 -0400
committerRalph Amissah <ralph.amissah@gmail.com>2026-09-22 14:23:53 -0400
commite1c9056903c2478066d5da8934d704f4d1935da4 (patch)
treef3fbfee319f3947e216f996d5097480bf82d65cb /src
parenttest (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.d13
-rw-r--r--src/sisudoc/ocda/abstraction/ssp_in.d2
-rw-r--r--src/sisudoc/ocda/io_in/read_source_files.d54
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;
}