<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Sean Bailey </title>
    <description>The latest articles on DEV Community by Sean Bailey  (@mountain_viewprovisions).</description>
    <link>https://dev.to/mountain_viewprovisions</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3759538%2F0ac1fbd9-a9c2-4872-b768-732523582d21.webp</url>
      <title>DEV Community: Sean Bailey </title>
      <link>https://dev.to/mountain_viewprovisions</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mountain_viewprovisions"/>
    <language>en</language>
    <item>
      <title>ArchivioMD: A Deep Dive into the Cryptographic Stack</title>
      <dc:creator>Sean Bailey </dc:creator>
      <pubDate>Sat, 28 Feb 2026 03:30:03 +0000</pubDate>
      <link>https://dev.to/mountain_viewprovisions/archiviomd-a-deep-dive-into-the-cryptographic-stack-2dc</link>
      <guid>https://dev.to/mountain_viewprovisions/archiviomd-a-deep-dive-into-the-cryptographic-stack-2dc</guid>
      <description>&lt;p&gt;&lt;em&gt;This is an update to my earlier post on ArchivioMD. Since that writeup, the plugin has landed on WordPress.org, gained a full algorithm suite, HMAC integrity mode, RFC 3161 trusted timestamping, and external anchoring to GitHub/GitLab. There's a lot to unpack.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  What the plugin actually does
&lt;/h2&gt;

&lt;p&gt;ArchivioMD started as a way to manage meta-documentation files — your &lt;code&gt;security.txt&lt;/code&gt;, &lt;code&gt;privacy-policy.md&lt;/code&gt;, &lt;code&gt;robots.txt&lt;/code&gt; and similar — from inside the WordPress admin. Every document gets a UUID and a checksum, every edit appends to an immutable changelog, and HTML rendering from Markdown is handled automatically.&lt;/p&gt;

&lt;p&gt;That's still the core. What's grown significantly is the cryptographic layer on top of it: how hashes are computed, how they're bound to identity, how they're externally verifiable, and how you can timestamp them against a trusted third party. That's what this post is about.&lt;/p&gt;




&lt;h2&gt;
  
  
  The hash helper: algorithm-agnostic from the start
&lt;/h2&gt;

&lt;p&gt;The entire hashing surface of the plugin runs through a single class, &lt;code&gt;MDSM_Hash_Helper&lt;/code&gt;. Every hash — post content, document checksums, anchor records — goes through this one entry point. That was an intentional design choice: swap the algorithm once, and it propagates everywhere.&lt;/p&gt;

&lt;h3&gt;
  
  
  The algorithm roster
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Standard (production-ready):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;sha256&lt;/code&gt; — SHA-256, the default&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;sha512&lt;/code&gt; — SHA-512&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;sha3-256&lt;/code&gt; — SHA3-256 (Keccak)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;sha3-512&lt;/code&gt; — SHA3-512&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;blake2b&lt;/code&gt; — BLAKE2b-512&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Experimental (with automatic fallback):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;blake3&lt;/code&gt; — BLAKE3-256&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;shake128&lt;/code&gt; — SHAKE128 with 256-bit output&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;shake256&lt;/code&gt; — SHAKE256 with 512-bit output&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The experimental algorithms come with a graceful degradation chain. If &lt;code&gt;blake3&lt;/code&gt; isn't natively available via a PHP extension, the plugin falls back to BLAKE2b-512, and if &lt;em&gt;that's&lt;/em&gt; not available, it falls back to SHA-256. The &lt;code&gt;fallback: true&lt;/code&gt; flag in the return array lets callers surface a warning rather than silently emitting a weaker hash than intended.&lt;/p&gt;

&lt;p&gt;BLAKE3 in particular required its own implementation class (&lt;code&gt;MDSM_BLAKE3&lt;/code&gt;) because PHP doesn't ship it natively. The HMAC construction for BLAKE3 is built manually using the standard &lt;code&gt;H(K XOR opad || H(K XOR ipad || message))&lt;/code&gt; structure, with the pure-PHP Blake3 hasher standing in for the compression function.&lt;/p&gt;

&lt;h3&gt;
  
  
  The packed string format
&lt;/h3&gt;

&lt;p&gt;All stored hashes use a self-describing format:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Standard:  "sha256:abcdef…"
HMAC:      "hmac-sha256:abcdef…"
Legacy:    "abcdef…"              (treated as bare SHA-256)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;unpack()&lt;/code&gt; method handles all three. Legacy bare-hex hashes from before v1.3 verify correctly against &lt;code&gt;hash()&lt;/code&gt; without any migration. The format tag in the packed string drives the entire downstream verification path — the global "active algorithm" setting is never consulted when verifying an existing hash.&lt;/p&gt;




&lt;h2&gt;
  
  
  HMAC Integrity Mode
&lt;/h2&gt;

&lt;p&gt;Standard hash verification answers: &lt;em&gt;has this content changed?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;HMAC integrity mode answers: &lt;em&gt;has this content changed, and was the original hash produced by someone with access to the secret key?&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  How it's configured
&lt;/h3&gt;

&lt;p&gt;The key lives in &lt;code&gt;wp-config.php&lt;/code&gt;, never in the database:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nb"&gt;define&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'ARCHIVIOMD_HMAC_KEY'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'your-long-random-secret-at-least-32-chars'&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then you enable the mode in Settings → Cryptographic Verification → HMAC Integrity Mode.&lt;/p&gt;

&lt;p&gt;The plugin enforces a minimum key length of 32 characters and surfaces a warning (not an error) if the key is shorter. A missing key when HMAC mode is enabled is a hard error — hash generation is blocked, not silently degraded.&lt;/p&gt;

&lt;h3&gt;
  
  
  What changes in the hash flow
&lt;/h3&gt;

&lt;p&gt;When HMAC mode is enabled, &lt;code&gt;compute_packed()&lt;/code&gt; calls &lt;code&gt;compute_hmac()&lt;/code&gt; instead of &lt;code&gt;compute()&lt;/code&gt;. The packed string gets the &lt;code&gt;hmac-&lt;/code&gt; prefix. Verification calls &lt;code&gt;hash_equals()&lt;/code&gt; against a freshly computed HMAC rather than a plain hash.&lt;/p&gt;

&lt;p&gt;One important consequence: if you rotate the key constant in &lt;code&gt;wp-config.php&lt;/code&gt;, every existing HMAC hash immediately fails verification. The old hashes don't become invalid &lt;em&gt;per se&lt;/em&gt; — the &lt;code&gt;hmac-sha256:&lt;/code&gt; prefix still tells the verifier what to do — but the key they were signed with is gone. This is by design. Key rotation is a deliberate action that invalidates the previous integrity chain.&lt;/p&gt;

&lt;h3&gt;
  
  
  The status helper
&lt;/h3&gt;

&lt;p&gt;There's an &lt;code&gt;hmac_status()&lt;/code&gt; method that returns a structured array for the admin UI: &lt;code&gt;mode_enabled&lt;/code&gt;, &lt;code&gt;key_defined&lt;/code&gt;, &lt;code&gt;key_strong&lt;/code&gt;, &lt;code&gt;ready&lt;/code&gt;, and a &lt;code&gt;notice_level&lt;/code&gt; / &lt;code&gt;notice_message&lt;/code&gt; pair for surfacing the right admin notice. The four states are: off, enabled-but-no-key, enabled-with-weak-key, and fully active.&lt;/p&gt;




&lt;h2&gt;
  
  
  Post content verification: deterministic hashing
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;MDSM_Archivio_Post&lt;/code&gt; class handles WordPress post and page content. The challenge here is determinism: the same logical content must always produce the same hash, regardless of whitespace normalization, line ending differences, or editor artifacts.&lt;/p&gt;

&lt;p&gt;The canonicalization pipeline strips the content down to a stable form before hashing. It also binds the hash to the post ID and author ID to prevent hash reuse — you can't take a hash generated for post 42 and pass it off as valid for post 99.&lt;/p&gt;

&lt;p&gt;The result is stored in post meta. On every save, if auto-generate is enabled, the hash is recomputed and logged to the audit table (&lt;code&gt;wp_archivio_post_audit&lt;/code&gt;). The audit table gained a &lt;code&gt;post_type&lt;/code&gt; column in v1.5.9, and the schema migration runs inline on construction if the column is missing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Verification badge
&lt;/h3&gt;

&lt;p&gt;Three badge states are possible:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;✓ Verified&lt;/strong&gt; (green) — current content hash matches the stored hash&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;✗ Unverified&lt;/strong&gt; (red) — content has changed since last hash generation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;− Not Signed&lt;/strong&gt; (gray) — no hash has been generated for this post&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Badges can be auto-injected below titles or content, or placed manually with the &lt;code&gt;[hash_verify]&lt;/code&gt; shortcode. Visitors can download a verification file containing the hash, algorithm, mode, post metadata, and (if available) RFC 3161 timestamp details.&lt;/p&gt;




&lt;h2&gt;
  
  
  RFC 3161 Trusted Timestamping
&lt;/h2&gt;

&lt;p&gt;This is the most involved feature in the 1.6.x series. RFC 3161 is the internet standard for cryptographic timestamping — you send a hash to a Time Stamp Authority, they sign it and return a timestamp token (a &lt;code&gt;.tsr&lt;/code&gt; file) that proves the hash existed at a specific point in time. The token is signed by the TSA's certificate chain, which anchors it to a trusted third party entirely outside your infrastructure.&lt;/p&gt;

&lt;h3&gt;
  
  
  Built-in TSA profiles
&lt;/h3&gt;

&lt;p&gt;The plugin ships with four profiles:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Provider&lt;/th&gt;
&lt;th&gt;URL&lt;/th&gt;
&lt;th&gt;Auth&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;FreeTSA.org&lt;/td&gt;
&lt;td&gt;&lt;code&gt;https://freetsa.org/tsr&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;None (rate-limited)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DigiCert&lt;/td&gt;
&lt;td&gt;&lt;code&gt;http://timestamp.digicert.com&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GlobalSign&lt;/td&gt;
&lt;td&gt;&lt;code&gt;http://timestamp.globalsign.com/tsa/r6advanced1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sectigo&lt;/td&gt;
&lt;td&gt;&lt;code&gt;https://timestamp.sectigo.com&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;None (throttle to 15s+)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A custom endpoint option is also available for private TSAs or enterprise deployments.&lt;/p&gt;

&lt;h3&gt;
  
  
  The timestamping flow
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Take the content hash from the anchor record.&lt;/li&gt;
&lt;li&gt;If the content algorithm is SHA-256 and the hash is 64 hex characters, use the raw bytes directly as the RFC 3161 MessageImprint. If it's any other algorithm, SHA-256-hash the hex string — this is recorded in the manifest as &lt;code&gt;"method": "sha256_of_hex"&lt;/code&gt; so you can reproduce the imprint independently.&lt;/li&gt;
&lt;li&gt;Build a DER-encoded &lt;code&gt;TimeStampReq&lt;/code&gt; in pure PHP — no external ASN.1 libraries required. The structure follows RFC 3161 §2.4.1: a version integer, a MessageImprint sequence (algorithm OID + hash bytes), a random 64-bit nonce, and &lt;code&gt;certReq TRUE&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;POST it to the TSA with &lt;code&gt;Content-Type: application/timestamp-query&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Receive the &lt;code&gt;TimeStampResp&lt;/code&gt;, check the &lt;code&gt;PKIStatus&lt;/code&gt; integer is 0 or 1, extract the serial and &lt;code&gt;genTime&lt;/code&gt; for logging.&lt;/li&gt;
&lt;li&gt;Store the &lt;code&gt;.tsr&lt;/code&gt; response and the original &lt;code&gt;.tsq&lt;/code&gt; request in &lt;code&gt;wp-content/uploads/meta-docs/tsr-timestamps/&lt;/code&gt;. A &lt;code&gt;.manifest.json&lt;/code&gt; file is written alongside them with everything needed for offline verification:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"content_hash_algorithm"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sha256"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"content_hash_hex"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"abcdef..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tsr_message_imprint"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"algorithm"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sha256"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"direct"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"note"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"TSR message data equals the raw content hash bytes."&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"verification_command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"openssl ts -verify -in file.tsr -queryfile file.tsq -CAfile /etc/ssl/certs/ca-certificates.crt"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;.tsr&lt;/code&gt; and &lt;code&gt;.tsq&lt;/code&gt; files are blocked from direct HTTP access via &lt;code&gt;.htaccess&lt;/code&gt;. They're served through an authenticated AJAX download handler instead. The manifest JSON is publicly accessible — it contains no secret data.&lt;/p&gt;

&lt;h3&gt;
  
  
  Offline verification
&lt;/h3&gt;

&lt;p&gt;Because the &lt;code&gt;.tsr&lt;/code&gt; is a standard RFC 3161 token, you can verify it with &lt;code&gt;openssl ts&lt;/code&gt; on any machine, independent of the WordPress installation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;openssl ts &lt;span class="nt"&gt;-verify&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-in&lt;/span&gt; document-20260214-143022.tsr &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-queryfile&lt;/span&gt; document-20260214-143022.tsq &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-CAfile&lt;/span&gt; /etc/ssl/certs/ca-certificates.crt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For FreeTSA you need to download their certificate separately (the &lt;code&gt;cert_url&lt;/code&gt; is in the manifest). For DigiCert, GlobalSign, and Sectigo, the root is already in your system trust store.&lt;/p&gt;




&lt;h2&gt;
  
  
  External Anchoring
&lt;/h2&gt;

&lt;p&gt;Separate from RFC 3161, the plugin can push cryptographic anchor records to GitHub or GitLab repositories. The idea is a distributed, tamper-evident audit trail: even if your WordPress database is compromised, the hashes are already committed to an external repository under a different trust domain.&lt;/p&gt;

&lt;p&gt;The anchoring system is provider-agnostic. Every provider implements &lt;code&gt;MDSM_Anchor_Provider_Interface&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;MDSM_Anchor_Provider_Interface&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt; &lt;span class="nv"&gt;$record&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt; &lt;span class="nv"&gt;$settings&lt;/span&gt; &lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;test_connection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt; &lt;span class="nv"&gt;$settings&lt;/span&gt; &lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;push()&lt;/code&gt; returns &lt;code&gt;['success' =&amp;gt; true, 'url' =&amp;gt; '...']&lt;/code&gt; or &lt;code&gt;['success' =&amp;gt; false, 'error' =&amp;gt; '...', 'retry' =&amp;gt; bool, 'rate_limited' =&amp;gt; bool]&lt;/code&gt;. The RFC 3161 provider implements the same interface, which is how multi-provider anchoring (added in v1.6.4) works — the queue just calls &lt;code&gt;push()&lt;/code&gt; on each enabled provider independently.&lt;/p&gt;

&lt;h3&gt;
  
  
  The queue
&lt;/h3&gt;

&lt;p&gt;Anchor jobs are persisted to &lt;code&gt;wp_options&lt;/code&gt; via &lt;code&gt;MDSM_Anchor_Queue&lt;/code&gt;. Key properties:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Hard cap at 200 jobs — prevents the options row from growing unbounded on high-volume sites&lt;/li&gt;
&lt;li&gt;Exponential backoff: 1 min → 2 → 4 → 8 → 16 minutes, up to 5 retries&lt;/li&gt;
&lt;li&gt;Transient-based locking (15-second TTL) to guard against two cron processes running simultaneously and double-processing the same job&lt;/li&gt;
&lt;li&gt;Per-provider state tracking (added in v1.6.4) so a failing Git provider doesn't block RFC 3161 jobs in the same queue entry&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Multi-provider anchoring
&lt;/h3&gt;

&lt;p&gt;Since v1.6.4, you can run GitHub/GitLab and RFC 3161 simultaneously. Each provider maintains independent retry state. Each writes its own entry to the Anchor Activity Log. If you're building a compliance evidence chain, you get a Git-hosted hash record &lt;em&gt;and&lt;/em&gt; a cryptographically timestamped &lt;code&gt;.tsr&lt;/code&gt; file for every anchor event.&lt;/p&gt;




&lt;h2&gt;
  
  
  WP-CLI
&lt;/h2&gt;

&lt;p&gt;As of v1.6.2, several operations are available from the CLI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Drain the anchor queue&lt;/span&gt;
wp archiviomd process-queue

&lt;span class="c"&gt;# Anchor a specific post&lt;/span&gt;
wp archiviomd anchor-post &lt;span class="nt"&gt;--post_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;42

&lt;span class="c"&gt;# Verify a post's content hash&lt;/span&gt;
wp archiviomd verify &lt;span class="nt"&gt;--post_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;42

&lt;span class="c"&gt;# Prune old anchor log entries&lt;/span&gt;
wp archiviomd prune-log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Log retention defaults to 90 days with automatic daily pruning via cron.&lt;/p&gt;




&lt;h2&gt;
  
  
  Compliance export
&lt;/h2&gt;

&lt;p&gt;Tools → ArchivioMD → Compliance Tools includes a structured JSON export that packages the full evidence chain for a given post or document: post metadata, hash history, anchor log entries, and inlined RFC 3161 TSR manifests. The intent is a self-contained artifact you can hand to an auditor or feed into a SIEM without them needing access to the WordPress installation.&lt;/p&gt;




&lt;h2&gt;
  
  
  Backward compatibility
&lt;/h2&gt;

&lt;p&gt;One thing I've been deliberate about: every hash format ever emitted by the plugin still verifies correctly against the current codebase.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Bare hex from before v1.3 → treated as &lt;code&gt;sha256&lt;/code&gt; / standard mode&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;sha256:hex&lt;/code&gt; from v1.3 → standard mode&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;hmac-sha256:hex&lt;/code&gt; from v1.4 → HMAC mode&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No migration scripts, no format conversion. The packed string carries all the information needed to verify it.&lt;/p&gt;




&lt;h2&gt;
  
  
  The plugin on WordPress.org
&lt;/h2&gt;

&lt;p&gt;It's live at [wordpress.org/plugins/archiviomd]&lt;/p&gt;

&lt;p&gt;1.6.6 will be on GitHub this weekend uploaded to WordPress after some test-drives &lt;/p&gt;

&lt;p&gt;Happy to answer questions about any of the implementation details — particularly the ASN.1 builder, the BLAKE3 fallback chain, or the queue concurrency model. Those were the three areas that took the most iteration to get right.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>productivity</category>
      <category>showdev</category>
      <category>development</category>
    </item>
    <item>
      <title>Building a WordPress Plugin for Markdown Docs and AI-Ready Site Files</title>
      <dc:creator>Sean Bailey </dc:creator>
      <pubDate>Sun, 08 Feb 2026 06:47:36 +0000</pubDate>
      <link>https://dev.to/mountain_viewprovisions/building-a-wordpress-plugin-for-markdown-docs-and-ai-ready-site-files-4lg</link>
      <guid>https://dev.to/mountain_viewprovisions/building-a-wordpress-plugin-for-markdown-docs-and-ai-ready-site-files-4lg</guid>
      <description>&lt;p&gt;I built this plugin while doing client work, not as a product idea.&lt;br&gt;
The client wanted WordPress because they were comfortable with it and the operational risk was low. That part was fine. The friction appeared when they needed a small set of Markdown documentation files to exist on the backend of the site.&lt;/p&gt;

&lt;p&gt;The catch: they were on managed hosting and had no FTP access.&lt;br&gt;
At that point, their realistic options were:&lt;/p&gt;

&lt;p&gt;Create and maintain a GitHub repository just to manage a few text files&lt;/p&gt;

&lt;p&gt;Convert documentation into WordPress pages and treat them as content&lt;br&gt;
Neither option felt appropriate for what were essentially infrastructure-level documents.&lt;/p&gt;

&lt;p&gt;That raised a broader question for me:&lt;/p&gt;

&lt;p&gt;Would someone who needs to routinely update documentation for audits, transparency, or compliance really want to manage it via FTP or GitHub if they don’t otherwise need those tools?&lt;/p&gt;

&lt;p&gt;Probably not.&lt;/p&gt;

&lt;p&gt;The idea: documentation as a first-class CMS concern&lt;/p&gt;

&lt;p&gt;The solution I landed on was to treat documentation files as managed artifacts inside the CMS, but still respect how the web expects those files to exist.&lt;/p&gt;

&lt;p&gt;The core requirements were:&lt;br&gt;
Edit Markdown files directly in WordPress&lt;/p&gt;

&lt;p&gt;Write files to the filesystem where possible&lt;/p&gt;

&lt;p&gt;Degrade gracefully when filesystem permissions are limited&lt;/p&gt;

&lt;p&gt;Keep the files usable outside of WordPress&lt;/p&gt;

&lt;p&gt;File placement strategy&lt;br&gt;
The plugin checks whether the WordPress root is writable:&lt;/p&gt;

&lt;p&gt;If root is writable:&lt;br&gt;
Markdown files are placed where they normally live (e.g. /README.md, /llms.txt).&lt;/p&gt;

&lt;p&gt;If root is not writable (common on managed hosting):&lt;/p&gt;

&lt;p&gt;Files are stored in a central, predictable location that can still be referenced and served.&lt;/p&gt;

&lt;p&gt;This avoids hard failures and removes the need for FTP entirely.&lt;/p&gt;

&lt;p&gt;From the site administrator’s perspective, they’re just editing documents in the admin panel. Where the file ends up is an implementation detail handled by the plugin.&lt;br&gt;
**&lt;br&gt;
Why Markdown works well here**&lt;/p&gt;

&lt;p&gt;Markdown is a good fit for documentation because it’s:&lt;br&gt;
Human-readable&lt;br&gt;
Diff-friendly&lt;br&gt;
Easy to audit&lt;/p&gt;

&lt;p&gt;Portable outside WordPress&lt;br&gt;
But raw .md files aren’t ideal for public consumption.&lt;/p&gt;

&lt;p&gt;So every Markdown file managed by the plugin automatically generates a corresponding HTML document.&lt;br&gt;
This serves two purposes:&lt;br&gt;
Readability – legal documents and policies are much easier to consume as HTML&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Indexability&lt;/strong&gt; – HTML documents are easier to surface via search engines and internal linking&lt;br&gt;
The Markdown file remains the source of truth. The HTML is a generated artifact.&lt;/p&gt;

&lt;p&gt;*&lt;em&gt;Curated document pages&lt;br&gt;
Many sites don’t just have one document. They have several:&lt;br&gt;
Privacy Policy&lt;br&gt;
Terms of Service&lt;br&gt;
Cookie Policy&lt;br&gt;
*&lt;/em&gt;&lt;br&gt;
Compliance or disclosure documents&lt;br&gt;
Instead of forcing each document to be a standalone page, the plugin allows site owners to curate a documentation page.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The workflow looks like this:&lt;/strong&gt;&lt;br&gt;
Create a blank WordPress page (e.g. /docs or /legal)&lt;/p&gt;

&lt;p&gt;Point the plugin to that page&lt;br&gt;
Select which Markdown documents to display&lt;/p&gt;

&lt;p&gt;Add a short description for each document&lt;/p&gt;

&lt;p&gt;The result is a clean, generated page that lists documents with:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Title&lt;br&gt;
Description&lt;br&gt;
Link to the .md file&lt;br&gt;
Link to the HTML-rendered version&lt;/strong&gt;&lt;br&gt;
This can be done via shortcode or by designating a page in the plugin settings.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Print and PDF support&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;One thing that comes up more often than people expect: printing documentation.&lt;br&gt;
W&lt;br&gt;
hether for internal audits or external compliance requests, users sometimes need a physical or archived copy. The plugin supports printing and saving rendered documents as PDFs from the front end.&lt;/p&gt;

&lt;p&gt;It’s not flashy, but it’s practical.&lt;br&gt;
Expanding beyond Markdown: raw text files that still matter&lt;/p&gt;

&lt;p&gt;Toward the end of development, I &lt;br&gt;
started thinking about other plain-text files that are still relevant but often scattered across plugins or manual edits.&lt;br&gt;
robots.txt&lt;/p&gt;

&lt;p&gt;Despite its age, robots.txt is still &lt;br&gt;
a meaningful signal source.&lt;br&gt;
It can:&lt;/p&gt;

&lt;p&gt;Control crawler inclusion/exclusion&lt;br&gt;
Help remove content from archive sites&lt;/p&gt;

&lt;p&gt;Express intent even when it isn’t strictly enforced&lt;/p&gt;

&lt;p&gt;The plugin provides clean access to edit and manage robots.txt without needing an SEO plugin.&lt;br&gt;
Sitemaps&lt;/p&gt;

&lt;p&gt;Sitemaps are another classic requirement.&lt;/p&gt;

&lt;p&gt;Rather than bundling SEO features, &lt;br&gt;
the plugin focuses on sitemap generation as a pure infrastructure concern:&lt;/p&gt;

&lt;p&gt;Generate a single sitemap&lt;br&gt;
Or split it into multiple sections for large sites&lt;br&gt;
No keyword tooling, no analytics — just accurate content signaling.&lt;br&gt;
llms.txt&lt;/p&gt;

&lt;p&gt;Finally, I added support for llms.txt.&lt;/p&gt;

&lt;p&gt;This file isn’t widely adopted yet, but awareness is growing. Conceptually, it’s similar to robots.txt, except it communicates intent to LLMs / AI systems.&lt;br&gt;
&lt;strong&gt;Depending on the site owner’s goals, it can be used to:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Request exclusion from training datasets&lt;/p&gt;

&lt;p&gt;Or signal that a site should be visited more frequently for accurate representation&lt;/p&gt;

&lt;p&gt;It’s not enforceable, but neither was robots.txt at the beginning. Intent signaling still matters.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why bundle all of this together?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Individually, these features already exist across multiple plugins.&lt;br&gt;
The point wasn’t to replace them — it was to offer a clean, centralized way to manage documentation and signaling files without:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;FTP access&lt;br&gt;
GitHub dependencies&lt;br&gt;
Bloated SEO tooling&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The plugin treats these files as infrastructure, not marketing assets.&lt;br&gt;
Open source by design&lt;/p&gt;

&lt;p&gt;The plugin is fully open source and designed to work within WordPress constraints rather than around them.&lt;br&gt;
It respects:&lt;/p&gt;

&lt;p&gt;*&lt;em&gt;Managed hosting limitations&lt;br&gt;
WordPress admin workflows&lt;br&gt;
Filesystem realities&lt;br&gt;
*&lt;/em&gt;&lt;br&gt;
If you’re interested, the code is available on GitHub, and feedback or contributions are welcome.&lt;br&gt;
Closing thoughts&lt;/p&gt;

&lt;p&gt;This started as a small solution to a repeating client problem, but it reflects a broader pattern:&lt;br&gt;
WordPress sites increasingly need to expose machine-readable documentation, not just human-facing pages.&lt;/p&gt;

&lt;p&gt;Markdown, plain-text files, and clear signaling are still part of the web’s foundation — even as platforms evolve.&lt;/p&gt;

&lt;p&gt;Sometimes the best tools aren’t flashy. They just remove friction.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/MountainViewProvisions/archiviomd/releases" rel="noopener noreferrer"&gt;Archiviomd&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>productivity</category>
      <category>development</category>
      <category>php</category>
    </item>
  </channel>
</rss>
