Decision log
Format per entry: ### D-nnn Title / Date / Status (accepted | amended-by D-xxx | superseded-by D-xxx | pending) / Context / Decision / Consequences / Spec refs / Tests. Append-only. Every test that encodes a decision cites its D-id in its docstring; every commit touching contract semantics references a D-id. Reviewed at every checkpoint (CP0..CP5). §2 of the spec is never reopened except by the owner (D-023).
D-001 Grouped MBR payment convention
Section titled “D-001 Grouped MBR payment convention”- Date: 2026-09-29 · Status: accepted
- Context: §12 says “exact MBR delta” and “overpayment accepted”; §20 says “equal”; ABI signatures lack the payment argument; some methods create boxes only sometimes.
- Decision: every method that MAY create a box or asset takes a leading
pay: gtxn.PaymentTransaction(always present; amount MAY be 0). Contract readspre = app.min_balancebefore creations andpostafter (acct_params_get AcctMinBalance), assertspay.sender == Txn.sender,pay.receiver == app,close_remainder_to == rekey_to == ZeroAddress,pay.amount >= post - pre. Overpayment kept (no refund in M1, §22). - Consequences: M1 methods with
pay: publish, vote_article, claim_reputation, add_field. Without: claim_article, new_version, set_status, set_governance. The payment sits at group index i-1 so one payment cannot serve two calls. - Spec refs: §12, §13, §17 inv 22, §20 · Tests: COST-01..07, INV22-01/02
D-002 M1 status-matrix scope and disputed
Section titled “D-002 M1 status-matrix scope and disputed”- Date: 2026-09-29 · Status: accepted
- Context: §7.3 rows 6-8 need
flag/resolve_dispute(M2);disputedis unreachable on LocalNet in M1. - Decision: M1 LocalNet matrix =
set_statusover all 25 pairs x {author, governance, stranger}; only the 5 author rows succeed;disputedas source or target always fails viaset_status. Disputed-gated negatives (new_version, vote_article, claim_reputation, claim_article) are offline tests seedingstatus = 4. Rows 6-8 and their LocalNet variants are@pytest.mark.m2(xfail) and listed in M1_REPORT.md. No test-only entry path on the immutable contract. - Spec refs: §7.3, §7.4, §19, §20, §21 · Tests: ST-01, ST-02, ST-04 (m2), VER-04, VOTE-05, CLAIM-04
D-003 Creation, governance initialisation, taxonomy seeding
Section titled “D-003 Creation, governance initialisation, taxonomy seeding”- Date: 2026-09-29 · Status: accepted (amended: global schema)
- Decision:
create(governance: address)declared@arc4.abimethod(create="require"), no bare create; assertsgovernance != ZeroAddress; stores globalsgovernance(bytes) andarticle_seq_next = 1(uint); emitsGovernanceChanged(1, ZeroAddress, governance, ts). Global schema = 1 uint + 1 bytes (PC-03 said 0 uints; the sequence counter of D-023 needs one). Deploy script seeds/spec/taxonomy.jsonwith groups of <= 8 (pay, add_field) pairs signed by the Falcon-1024 governance account, fees per D-027. No update/delete paths. - Spec refs: §4, §11, §12.1, §19, §20 · Tests: test_create_governance, TAX-05/07, SEC-04..08
D-004 claim_reputation with nothing grantable today
Section titled “D-004 claim_reputation with nothing grantable today”- Date: 2026-09-29 · Status: accepted
- Decision: fail when
granted_primary == 0(“cap exhausted today”), in addition to failing whenclaimable == 0. Every successful claim therefore emits >= 1ReputationChanged. - Spec refs: §10.2, §11, §20 · Tests: CAP-06, CLAIM-02
D-005 claim_article status rule
Section titled “D-005 claim_article status rule”- Date: 2026-09-29 · Status: accepted
- Decision: allowed in preprint, under_review, final, disputed; explicit assert-fail in retracted (otherwise fails opaquely because manager is ZeroAddress). Explicit opt-in assert for a clear error. §7.4 gains a
claim_articlecolumn;set_statusfollows §7.3. - Spec refs: §7.1, §7.4, §12 · Tests: ASA-04/05/13
D-006 Timestamps, updated_at, reputation-box writes
Section titled “D-006 Timestamps, updated_at, reputation-box writes”- Date: 2026-09-29 · Status: accepted
- Decision: all
ts,created_at,updated_at,epoch_dayderive from the singleGlobal.latest_timestampof the call.article.updated_at := tsin every method that writes the article box (publish: equals created_at). A reputation box is written only on creation (rep 0, updated_at = ts, cap_day = epoch_day, cap_today = 0) and whengranted > 0. Zero grants never touch a reputation box, so event replay stays exact. - Spec refs: §2 Decay, §6.4, §10.2, §11, §20 · Tests: REP-04, EVT-03, VOTE-06
D-007 Inner asset-config parameter sets
Section titled “D-007 Inner asset-config parameter sets”- Date: 2026-09-29 · Status: accepted (amended by D-024: reserve = CID digest; amended by D-033: Revoked freeze)
- Context: go-algorand replaces each non-zero address with the transaction’s value; an omitted address becomes ZeroAddress irreversibly; an already-zero address silently stays zero.
- Decision: every inner AssetConfig passes manager, reserve, freeze and clawback explicitly. Held (claim_article step 4) = manager app, reserve digest(current_cid), freeze Zero, clawback Zero. Version bump (new_version) = manager app, reserve digest(new_cid), freeze (Zero if claimed else app), clawback Zero. Revoked: see D-033. No self-unfreeze is needed (creator holding is never frozen by default_frozen).
- Spec refs: §7.1, §20 · Tests: ASA-01/02/03, VER-01 (inner fields), claimed x unclaimed variants
D-008 ASA url and metadata_hash
Section titled “D-008 ASA url and metadata_hash”- Date: 2026-09-29 · Status: superseded-by D-024
- Original: on-chain base32
ipfs://url with op-up. Superseded: the url is the constant ARC-19 template; no base32 on-chain, no ensure_budget, no extra argument.metadata_hashstays empty.
D-009 type / parent_article rule
Section titled “D-009 type / parent_article rule”- Date: 2026-09-29 · Status: accepted
- Decision: contract enforces
(article_type == 3) <=> (parent_article != 0); parent must exist (article box present), any type, any status; caller passes the parent box reference. notes/dataset/replication use parent = 0 and cite targets inreferences[]. - Spec refs: §6.4, §7.5, §14.2, §20 · Tests: LC-08, SEC-03
D-010 add_field validation
Section titled “D-010 add_field validation”- Date: 2026-09-29 · Status: accepted (amended-by D-047)
- Decision:
field_id != 0; boxt+id must not exist (append-only, no rename);1 <= len(name) <= 64; box value = raw UTF-8 name; no structural check on the id; payment per D-001. - Spec refs: §4, §20 · Tests: TAX-04, TAX-06
D-011 Reputation boxes created by claim_reputation
Section titled “D-011 Reputation boxes created by claim_reputation”- Date: 2026-09-29 · Status: accepted
- Decision: create the primary box if missing; if
secondary_field != 0create the secondary box if missing, regardless of the granted amounts. MBR delta depends only on box existence (client-computable). - Spec refs: §10.2, §10.5, §13.3 · Tests: CLAIM-01/05, COST-03
D-012 set_governance guards and PQ-compliance check
Section titled “D-012 set_governance guards and PQ-compliance check”- Date: 2026-09-29 · Status: accepted (amended: SDK check primary)
- Decision: sender == governance;
new != ZeroAddress;new != current; emitsGovernanceChanged(prev, new, ts). PQ-ness is asserted off-chain, primarily withnot is_ed25519_point(decode_address(addr))(py-algorand-sdk);docker exec ... algokey pq check-addressis an optional extra (binary presence in the algod image unverified). §12.1 wording fixed: check-address exit 0 == compliant. - Spec refs: §12, §12.1, §20 · Tests: SEC-07, GOV-01, GOV-04
D-013 Fees, inner counts, invariant-22 sequence
Section titled “D-013 Fees, inner counts, invariant-22 sequence”- Date: 2026-09-29 · Status: accepted (amended: new_version 1 inner; app funded with exactly 100 000)
- Decision: every inner txn
fee = 0; outer fee = (1 + n_inner) x min_fee for Ed25519 senders, (3 + n_inner) x min_fee for pqsig senders (D-027). Inner counts: publish 1, claim_article 4, new_version 1 (ARC-19 reserve update), set_status->retracted 1. The application account is funded with exactly 100 000 µAlgo at creation, so invariant 22 is asserted asspendable == 0before and after every step. Sequence INV22-01: add_field x3 (PQ), publish, claim_article, new_version, set_status x2, vote_article x7, claim_reputation x2 across two epoch days, set_governance PQ->PQ2->PQ, author close-out, second publish + retraction, failing claim_article and claim_reputation. INV22-02: overpayment grows spendable by exactly the surplus. - Spec refs: §13, §17 inv 22, §20 · Tests: INV22-01/02, COST-05/06
D-014 publishing-guide.md
Section titled “D-014 publishing-guide.md”- Date: 2026-09-29 · Status: accepted (owner)
- Decision: the file does not exist; M1 writes a skeleton from §14.5 (canonical params, hardened Kubo command, Path B checklist, CID -> 36 bytes, “TODO M4” markers for Pinata CAR and gateway checks); M4 completes it.
- Spec refs: §14.4, §14.5, §23
D-015 publish initial values
Section titled “D-015 publish initial values”- Date: 2026-09-29 · Status: accepted
- Decision: status 1, status_before_dispute 0, version 1, prev_cid 36 x 0x00, review_seq_next = comment_seq_next = 1, dispute_round 1, round_flag_weight 0, vote_total = vote_count = 0, author_count 1, invited_count 0 (submitter not counted; M2 invite_coauthor increments; max 65 535), claimed false, created_at = updated_at = ts, asa_id = created asset; submitter co-author box invited_at = accepted_at = ts, accepted true, claimed_total 0. (TD matrix row M1-LC-01 “invited_count = 1” is not adopted.)
- Spec refs: §6.4, §7.1, §7.6 · Tests: LC-01, LC-04
D-016 Field widths and saturating arithmetic
Section titled “D-016 Field widths and saturating arithmetic”- Date: 2026-09-29 · Status: accepted
- Decision: keep §6.4 widths; compute in uint64, narrow on store with
arc4.UInt16/UInt32(fails on overflow); static assertREP_CAP_DAY <= 65_535;cap_remainingis saturating; new_version fails at version 65 535. - Spec refs: §5, §6.4 · Tests: test_versions (overflow), CAP tests
D-017 ARC-4 naming
Section titled “D-017 ARC-4 naming”- Date: 2026-09-29 · Status: accepted
- Decision:
bytes36=byte[36](arc4.StaticArray[arc4.Byte, Literal[36]]); bools =arc4.Bool; Python parameterarticle_type(selectors use types only). Event structs are constructed explicitly withschema_version = arc4.UInt8(1); no struct field defaults. - Spec refs: §6.4, §11, §12 · Tests: EVT-02 (static)
D-018 Resource references
Section titled “D-018 Resource references”- Date: 2026-09-29 · Status: accepted
- Decision: M1_REPORT lists per-method box/asset/account references, including absent boxes that must be referenced (voter’s
auandpin vote_article; parent box in amendment publish; ASA in claim_article, new_version, retraction). Layer C asserts the populated list;txn.Accessis not relied on. - Spec refs: §6.2, §21 C · Tests: SIM-01/03
D-019 Epoch-day testing
Section titled “D-019 Epoch-day testing”- Date: 2026-09-29 · Status: accepted
- Decision: rollover in offline tests with patched
latest_timestamp; on LocalNet viaalgod.set_timestamp_offset(py-algorand-sdk, dev mode) with a cumulative, session-scoped offset that only ever increases and is never reset; semantics probed in test_00; fresh LocalNet per full run. - Spec refs: §10.2, §20 · Tests: CAP-01/02 (A), CAP-03 (B)
D-020 M2 pre-decisions (recorded now, implemented in M2)
Section titled “D-020 M2 pre-decisions (recorded now, implemented in M2)”- Date: 2026-09-29 · Status: accepted
Flaggedgainsnew_status: uint8;resolve_commentfollows the vote_article column;comment()in retracted still creates the commenter’s reputation box; reviews stay allowed indisputed;accept_coauthorshipblocked once final;mentionsis inlinearc4.DynamicArray[arc4.Address](<= 5; Commented with 5 mentions = 278 B);round_flag_weightkeeps accumulating while disputed.- Spec refs: §7.4, §8.3, §9, §11, §22
D-021 new_version edge cases
Section titled “D-021 new_version edge cases”- Date: 2026-09-29 · Status: accepted
- Decision: only consecutive CIDs must differ (A->B->A allowed); version overflow fails (D-016).
- Spec refs: §7.2, §17 inv 7 · Tests: VER-02, test_versions
D-022 Vocabulary
Section titled “D-022 Vocabulary”- Date: 2026-09-29 · Status: accepted
- submitter = article.author (editing/status/invite rights); accepted author = submitter or co-author with accepted = true (claim rights, self-action rules); commenter = comment.author; reviewer = review.reviewer.
ReputationClaimed.authormeans the claimant.
D-023 article_id is a contract-assigned sequence (OWNER decision; reopens §2 “Article identity”)
Section titled “D-023 article_id is a contract-assigned sequence (OWNER decision; reopens §2 “Article identity”)”- Date: 2026-09-29 · Status: accepted (owner)
- Context: box names must be known at signing time; the id of an inner-created ASA is the ledger txn counter + 1 and is unpredictable on public networks (AVM-01).
- Decision:
article_id= global sequencearticle_seq_next(starts at 1), NOT the ASA id. Boxes are keyed by the sequence (a+u64(seq),au+u64(seq)+addr,va+u64(seq)+addr; M2 keys likewise); the article box storesasa_id: uint64(appended field, 192 B, MBR 82 900 µAlgo, publish deposit 212 200);Publishedcarriesarticle_idandasa_id; the ASA is still created (ARC-71). All ABI methods takearticle_id= sequence; methods needing the asset readasa_idfrom the box. - Consequences: §2 table text is not edited; the amendment is recorded in the spec copy changelog. §6.2, §6.4, §11, §12, §13.3, §20 amended in the copy.
- Spec refs: §2, §6.2, §6.4, §11, §12, §20 · Tests: LC-01, ASA-14, KEY-01
D-024 ARC-19 from M1 (OWNER decision; supersedes D-008)
Section titled “D-024 ARC-19 from M1 (OWNER decision; supersedes D-008)”- Date: 2026-09-29 · Status: accepted (owner)
- Context: the ASA
urlis immutable after creation, so ARC-19 must be chosen at creation. - Decision:
url = "template-ipfs://{ipfscid:1:dag-pb:reserve:sha2-256}"(51 bytes, constant);reserve = Account(cid.bytes[4:36])(the 32-byte sha2-256 digest of the current CID as an address);new_versionupdatesreservethrough one inner AssetConfig that re-passes manager/freeze/clawback explicitly; no base32 on-chain;metadata_hashempty. ARC-71 allows reserve = ARC-19 metadata. ARC-19 is marked superseded-by ARC-89; the article box remains the authority (revisit ARC-89 in M5). - Spec refs: §7.1, §7.2, §22 · Tests: ASA-01/02/03, VER-01 (reserve == digest), tests/ref/cid.py (ARC-19 rebuild)
D-025 LocalNet runs locally (OWNER decision)
Section titled “D-025 LocalNet runs locally (OWNER decision)”- Date: 2026-09-29 · Status: accepted (owner)
- Decision: enable SVM in the Gigabyte BIOS, install WSL 2 (
wsl --install --no-distribution,--web-downloadfallback) and Docker Desktop (WSL 2 backend, auto-update off, version recorded). GitHub Actions / remote LocalNet throughALGOD_SERVER/ALGOD_TOKEN/KMD_*is an optional later backup, never a prerequisite. Offline tests and compilation must work before Docker exists (Phase 0 Track 1, Phases 1-4). - Host facts: Windows 10 Pro 22H2 19045, Ryzen 5 2600, 16 GB, ~32 GB free, virtualization disabled in firmware at start, WSL absent, Python 3.14.5 + 3.8.5, Node 26.5.1, git 2.54.
- Spec refs: §3, §21
D-026 Storage provider: Pinata replaces web3.storage (OWNER decision)
Section titled “D-026 Storage provider: Pinata replaces web3.storage (OWNER decision)”- Date: 2026-09-29 · Status: accepted (owner)
- Decision: web3.storage/Storacha is decommissioned (writes disabled 2026-05-15). Spec copy §3/§14.4 reference Pinata (CAR upload: paid plan, public network, single root, async) plus a second provider to be chosen in M5 (Filebase CAR import or Filecoin Pin, to verify). Affects M4/M5 only.
- Spec refs: §3, §14.4, §22
D-027 Fee model for pqsig senders
Section titled “D-027 Fee model for pqsig senders”- Date: 2026-09-29 · Status: accepted (amended-by D-045)
- Context: spec §12.1/§13.2 describe a per-byte surcharge; go-algorand v42 prices a Falcon-1024 signature as a flat +2 000 000 usage; per-byte pricing only applies to oversize note/args/programs.
- Decision:
usage = 1e6per outer txn (+2e6 if pqsig-signed) + 1e6 per inner txn;fee = ceil(min_fee x usage / 1e6), min_fee read from algod. Flow for every PQ-signed group: compute expected usage -> sign with Falcon -> simulate the SIGNED group -> asserttxn-groups[0]["group-usage"] == expected-> send. PQ senders usestatic_fee; never rely oncover_app_call_inner_transaction_feesalone for them; never mutate the fee after grouping (re-sign instead). Spec copy §12.1/§13.2 corrected. - Spec refs: §12.1, §13.2, §20 · Tests: GOV-02/03, SIM-04, tests/ref/pqfee.py
D-028 Version pins and bootstrap smoke test
Section titled “D-028 Version pins and bootstrap smoke test”- Date: 2026-09-29 · Status: accepted (amended-by D-046)
- Pins: AlgoKit CLI 2.10.2 (pipx); Python 3.12.10; puyapy 5.10.1 + algorand-python 4.0.0; algorand-python-testing 1.1.0; algokit-utils 4.2.3; algokit-client-generator 2.2.0; py-algorand-sdk 2.12.0; algorand-falcon 0.1.0; template algokit-python-template 1.8.1 (
_commitin .copier-answers.yml); LocalNetalgorand/algod:5.0.2-stable@sha256:3a2c9266b61e414e770aa69bd3d3867f8b350177c7af31df251db36ef70405a4; Node 26.5.1 for the M1 CID script, Node 24 LTS for M3/M4; ipfs-unixfs-importer 17.1.1, multiformats 14.0.5, blockstore-core 7.0.1; python-dotenv, hypothesis, Poetry and every dev tool pinned exactly after the firstpoetry lock; poetry.lock committed.puyapy --target-avm-versionleft at the compiler default, value read at bootstrap and recorded here:<fill>. Never use algokit-utils 5 betas, CLI 2.11 beta or algorand-python-testing 1.2 betas. - Smoke test (2026-09-29): PASSED for the template hello-world (puyapy 5.10.1 + algorand-python 4.0.0 compile, ARC-56 + typed client generated, algorand-python-testing 1.1.0 test green on Python 3.12.14); emulator feature probe in tests/unit/test_smoke_emulator.py. Original wording: template hello-world builds and its offline test runs;
tests/unit/test_smoke_emulator.py(BoxMap(Bytes, arc4.Struct), itxn.AssetConfig created_asset, arc4.emit, op.sqrt, AcctParamsGet, gtxn payment arg, patched latest_timestamp). Fallback if it fails: puyapy 5.9.0 + algorand-python 3.5.1 (re-lock, log here); last resort Layer A restricted to pure subroutines. - Spec refs: §3, §23.2
D-029 Repo-owned, digest-pinned LocalNet
Section titled “D-029 Repo-owned, digest-pinned LocalNet”- Date: 2026-09-29 · Status: accepted
- Decision:
algokit localnet start --name ariadneis run once to generate docker-compose.yml, algod_config.json and algod_network_template.json; they are copied to/localnet/, the algod image is pinned by digest, indexer/conduit/postgres services are removed for M1 (algod + kmd only; re-added pinned in M3: indexer 3.10.0 / conduit 1.10.0 by observed digests), ports 4001/4002, tokenax64, DevMode true. Operated withdocker compose -f localnet/docker-compose.yml -p ariadne up -d --wait | down -v. Neveralgokit localnet reset. First LocalNet test asserts/v2/statuslast-version ends in268b63433a907455d439995bf916f6b296018f4fand a pqsig txn is accepted. Docker Desktop version recorded here:<fill>. - Spec refs: §3 · Tests: test_00_node, test_00_pq_spike
D-030 ASA names
Section titled “D-030 ASA names”- Date: 2026-09-29 · Status: accepted
- Decision:
asset_name = "Ariadne Article",unit_name = "ARIA"(constants, §7.1 literal). Putting the sequence in the name (AVM-01 option A,"Ariadne #<seq>") stays a §22 open question, to be revisited only if the measured publish budget stays <= 600 ops with an on-chain itoa. - Spec refs: §7.1, §22 · Tests: ASA-01
D-031 Explicit Bytes box keys and arc4.Struct values
Section titled “D-031 Explicit Bytes box keys and arc4.Struct values”- Date: 2026-09-29 · Status: accepted
- Decision: all BoxMaps are
BoxMap(Bytes, <arc4.Struct>, key_prefix=b"<prefix>")with keys built by@subroutinebuilders (uint64 viaop.itob, uint16 viaarc4.UInt16(x).bytes, addresses via.bytes); tuple-typed keys are undocumented in Puya. §6.2 byte layout is normative and unit-tested (11 builders: 3, 9, 42, 17, 42, 17, 42, 50, 50, 43, 35). Field box value = raw UTF-8 bytes. Box values arearc4.Structso ARC-4 tuple sizes are normative (a 192, au 25, va 24, p 22). - Spec refs: §6.2, §6.4, §13.3 · Tests: KEY-01/02/03, tests/static/test_arc56.py
D-032 Publish box references on public networks
Section titled “D-032 Publish box references on public networks”- Date: 2026-09-29 · Status: accepted
- Decision: clients read
article_seq_nextright before signingpublishand build thea/aubox names from it; on TestNet/MainNet they reference seq and seq+1 (<= 7 refs incl. parent) and retry on failure. Tests usepopulate_app_call_resources; LocalNet dev mode is sequential so no collision occurs. - Spec refs: §12, §21.C · Tests: SIM-01/03 (footprint recorded)
D-033 Revoked AssetConfig parameter set (amends D-007)
Section titled “D-033 Revoked AssetConfig parameter set (amends D-007)”- Date: 2026-09-29 · Status: accepted
- Decision: on every transition to retracted the inner AssetConfig passes manager = Zero, reserve = digest(current_cid), freeze = Zero unconditionally, clawback = Zero. One code path for claimed and unclaimed articles; the ASA becomes immobile forever (claim_article is forbidden in retracted, D-005). go-algorand silently ignores a new value for an already-zero freeze, which is verified at the spike. Tests assert all four addresses after retraction in claimed x unclaimed variants.
- Spec refs: §7.1, §20 · Tests: ASA-03 (x6)
D-034 Taxonomy content
Section titled “D-034 Taxonomy content”- Date: 2026-09-29 · Status: accepted
- Decision:
/spec/taxonomy.json= 42 OECD FOS subfields (6 areas: 7+11+5+5+9+5),field_id = area << 8 | subfield, source “OECD Frascati Manual 2015 Table 2.2”;0x0303= Medical and health sciences / Health sciences with notes “includes sport and fitness sciences” (§4 example reworded in the spec copy);0x0304Medical biotechnology with alias Health biotechnology. Area ids are not registered on-chain; 0 reserved. Loader test enforces uniqueness, area byte 1-6, subfield byte >= 1, names 1-64 bytes. - Spec refs: §4, §14.2 · Tests: TAX-07, tests/static/test_taxonomy.py
D-035 §14.5 completions and line-ending policy
Section titled “D-035 §14.5 completions and line-ending policy”- Date: 2026-09-29 · Status: accepted
- Decision: canonical UnixFS parameters are completed with: max 174 links per file node, directory HAMT threshold 256 KiB estimated by
links-bytes, fanout 256 (8 bits),links-firstfield order, no mode/mtime, hidden files excluded, entries sorted by name, root = the added directory itself (never-w).compute-cid.mjspasses every importer option explicitly (wrapWithDirectory: truewith paths relative to the directory). Hardened Kubo reference command adds--max-file-links=174 --max-directory-links=0 --max-hamt-fanout=256on a fresh repo without an import profile (Kubo 0.43.1, optional cross-check). Repocore.autocrlf = false, root.gitattributes* text=auto eol=lf,spec/fixtures/** -text; a test asserts no\rin the fixtureindex.md. - Spec refs: §14.5, §23.3 · Tests: CID-08, tests/static/test_fixture_cid.py
D-036 networks.json governance field and deploy rewrite
Section titled “D-036 networks.json governance field and deploy rewrite”- Date: 2026-09-29 · Status: accepted
- Decision: each network entry gains
governance(address string). The deploy script rewrites the localnet entry on every deploy (genesisId and genesisHash fromalgod.versions(), appId, governance) because the LocalNet genesis changes on every reset. MainNet/TestNet entries carry the known genesis hashes, appId 0, enabled false until M4. - Spec refs: §3.2, §12.1, §15 · Tests: GOV-04 (static), deploy test
D-037 ARC-28 decoding and replay reducer
Section titled “D-037 ARC-28 decoding and replay reducer”- Date: 2026-09-29 · Status: accepted
- Decision: neither algokit-utils 4.2.3 nor the typed clients decode ARC-28 events.
tests/ref/events.pybuilds the prefix map from the ARC-56eventslist (sha512_256(signature)[:4]), decodes withalgosdk.abi.ABITypetuples and skips the ARC-4 return log0x151f7c75.tests/ref/replay.pyrebuilds article and reputation projections from events and is the projection spec for the M3 indexer (which uses algokit-subscriber). Spec copy §21 reworded accordingly. - Spec refs: §11, §15, §20, §21 · Tests: EVT-02/03, REP-04
D-038 Deployment without an indexer
Section titled “D-038 Deployment without an indexer”- Date: 2026-09-29 · Status: accepted
- Context: algokit-utils
factory.deploy()resolves existing apps through the indexer (creator lookup); M1 LocalNet has no indexer (D-029). - Decision: the deploy script calls the typed factory’s explicit
createmethod (factory.send.create.create(args=(governance,)), name per the generated client), funds the app account with exactly 100 000 µAlgo, seeds the taxonomy idempotently (skips fields whosetbox exists) and persists appId/genesisHash/governance inspec/networks.json; a later deploy reuses the app when the stored genesisHash equals the node’s andapplication_info(appId)succeeds. Noon_update/on_schema_breakpolicy is involved; the contract is immutable anyway. TestNet/MainNet governance keys come fromalgokey pq generateoutside the repo. - Spec refs: §12.1, §23 · Tests: idempotence step of the end-to-end run, TAX-05/07
D-039 Test layers and the Layer C gate
Section titled “D-039 Test layers and the Layer C gate”- Date: 2026-09-29 · Status: accepted
- Decision: Layer A =
tests/unit(algorand-python-testing, seeded boxes incl. status = disputed, patched latest_timestamp); Layer S =tests/static(ARC-56 JSON: no Update/Delete actions,bareActions.call == [], events == §11 amended, struct sizes == ref/mbr.py; taxonomy; fixture); Layer B =tests/localnet(typed client on the pinned LocalNet); Layer C runs inside every B send: simulate with unnamed resources, assertunnamed-resources-accessedempty after population,app-budget-consumed <= 600(700 is the consensus hard limit), box refs <= 8, log count <= 32 and bytes <= 1 024,group-usagerecorded; then send. Layer A limits documented intests/README.md: the emulator does not compute MBR, does not apply asset reconfig/transfer/freeze, cannot enforce opt-in; A asserts submitted inner-txn fields (ctx.txn.last_group.get_itxn_group(i)); MBR amounts, ARC-71 authority, opt-in and invariant 22 are Layer B only. CID length 35/37 is tested in A with raw bytes and in B with a hand-built app_args transaction. - Spec refs: §21 · Tests: SIM-01..04, CID-06, tests/README.md
D-040 DECISIONS.md discipline
Section titled “D-040 DECISIONS.md discipline”- Date: 2026-09-29 · Status: accepted
- Decision: this file is append-only; entries use the header format; tests encoding a decision cite the D-id in their docstring; commits touching contract semantics reference a D-id; the file is reviewed at every checkpoint; superseded entries keep their text with a Status pointer. The root ARIADNE_SPEC.md is never edited;
spec/ARIADNE_SPEC.mdcarries<span class="spec-edit">[EDIT D-xxx]</span>tags and a changelog. - Spec refs: §23.6
D-041 PQ signing path (filled after the Phase 5 spike)
Section titled “D-041 PQ signing path (filled after the Phase 5 spike)”- Date: 2026-09-29 · Status: accepted (outcome in D-053)
- Candidates: (1) algokit-utils 4.2.3 composer with
Falcon1024AlgorandSignerregistered viaset_signerandstatic_fee; (2) raw py-algorand-sdkAtomicTransactionComposer; (3)docker exec ... algokey pq signfrom pytest. Record: signer class name found inalgosdk.signer, whether unsigned simulate omits the +2e6 usage,static_feevscover_app_call_inner_transaction_feesinterplay,set_timestamp_offsetsemantics, presence ofalgokeyin the image. An Ed25519 governance account for the bulk suite is not a valid M1 end state (§20). - Spec refs: §12.1, §13.2, §20 · Tests: test_00_pq_spike (GOV-01..03, SIM-04)
D-042 No ARC-3 / ARC-69 metadata in M1
Section titled “D-042 No ARC-3 / ARC-69 metadata in M1”- Date: 2026-09-29 · Status: accepted
- Context: ARC-71 says it extends ARC-3/ARC-69; a bare ASA with an ARC-19 template url is neither, so wallets and explorers show a plain ASA. ARC-3 would force a metadata JSON into the article directory; ARC-69 would need a note on the creation inner txn. ARC-19 and ARC-69 are both marked superseded-by ARC-89.
- Decision: no ARC-3/ARC-69 metadata in M1. The ASA carries only the ARC-71 parameters and the ARC-19 template url; the article box is the authority (§2). Revisit together with ARC-89 in M5.
- Spec refs: §2, §7.1, §22
D-043 Governance key custody and generation
Section titled “D-043 Governance key custody and generation”- Date: 2026-09-29 · Status: accepted (owner default)
- Decision: LocalNet uses an ephemeral Falcon-1024 key per test run (deterministic seed sha256(“ariadne-localnet-governance”), never reused elsewhere). TestNet/MainNet governance keys are generated with
algokey pq generateoutside the repo (algod container or WSL); the mnemonic is backed up offline by the owner; only the address entersspec/networks.jsonand this file. Before M4 a test proves algokey <-> py-algorand-sdk (mnemonic.to_pq_seed+ algorand-falcon) address interoperability. The “operator” of §12.1 is the owner (single signer) until native PQ multisig exists. - Spec refs: §12.1, §3.2 · Tests: GOV-01, GOV-04, interop test (M4 gate)
D-044 Retraction forfeits unclaimed article-vote reputation
Section titled “D-044 Retraction forfeits unclaimed article-vote reputation”- Date: 2026-09-29 · Status: accepted (documented behaviour, no change)
- Context: §7.4 marks
claim_reputationas not allowed inretractedandretractedis terminal (§7.3), so votes accumulated before retraction can never be materialised. - Decision: keep the spec behaviour. Document it here and in the web app (M4): the retract action shows the forfeited claimable amount before signing; the indexer profile shows it as “forfeited” rather than “claimable”.
- Spec refs: §7.3, §7.4, §10.2, §15, §16
D-045 Fee sizing with a scheme-only placeholder pqsig (amends D-027)
Section titled “D-045 Fee sizing with a scheme-only placeholder pqsig (amends D-027)”- Date: 2026-09-29 · Status: accepted (to verify at the Phase 5 spike)
- Context:
ledger/simulation/simulator.go(v5.0.2-stable) documents two placeholder PQ signature types: “Scheme-only placeholder: Scheme set, PublicKey empty, Signature empty, for fee surcharge calculation” and “Full placeholder: Scheme, Salt and PublicKey set”. A completely blank pqsig is proxy-signed with Ed25519 by the simulator and therefore omits the +2 000 000 usage. - Decision:
tests/ref/pqfee.pygetssimulate_usage(algod, txns, pq_senders)that wraps each unsigned transaction of a PQ sender in a SignedTransaction carryingPQSig(scheme=b"f1")and calls algod simulate with allow-empty-signatures to readgroup-usageBEFORE Falcon signing (algokit-utils 4.2.3 cannot attach a placeholder).static_fee = ceil(min_fee * usage / 1e6), cross-checked against the deterministic formula of D-027. After signing, the signed group is simulated again and the same usage asserted (D-027 flow). Recorded outcome of the spike:. - Spec refs: §12.1, §13.2, §20 · Tests: GOV-02, SIM-04, test_00_pq_spike (d)
D-046 Python 3.12 provisioned with uv (amends D-028)
Section titled “D-046 Python 3.12 provisioned with uv (amends D-028)”- Date: 2026-09-29 · Status: accepted
- Context: the host had only Python 3.14.5 and 3.8.5; installing 3.12 from python.org needs an owner action. puyapy/algorand-python require >=3.12,<4 and their CI covers 3.12/3.13 only, so 3.14 was not used.
- Decision:
uv(installed with pipx on Python 3.14) provisions a python-build-standalone CPython 3.12.x under %APPDATA%\uv\python\ without administrator rights. AlgoKit CLI and Poetry are installed with pipx on that interpreter; Poetry uses it for the project venv (poetry env use <path>). Recorded on 2026-09-29: pipx 1.17.7, uv 0.12.20, CPython 3.12.14 (%APPDATA%\uv\python\cpython-3.12.14-windows-x86_64-none\python.exe), AlgoKit 2.10.2, Poetry 2.5.1, pipx bin dir %USERPROFILE%.local\bin. A python.org 3.12.x installer remains an equivalent alternative. - Spec refs: §3, §23.2
D-047 Field name length limit is 96 bytes (amends D-010)
Section titled “D-047 Field name length limit is 96 bytes (amends D-010)”- Date: 2026-09-29 · Status: accepted
- Context: D-010 proposed
1 <= len(name) <= 64, but the OECD name “Electrical engineering, electronic engineering, information engineering” (2.2, 0x0202) is 71 bytes. Renaming an OECD field would break the “names follow the 2007/2015 wording” rule of D-034. - Decision:
MAX_FIELD_NAME = 96bytes (UTF-8). Field box MBR for the longest name = 2 500 + 400 x (3 + 71) = 32 100 µAlgo; FieldAdded log = 11 + 71 = 82 bytes; both far inside limits.spec/taxonomy.schema.jsonuses maxLength 96. - Spec refs: §4, §12, §13.2 · Tests: TAX-04, TAX-06, tests/static/test_taxonomy.py
Version pins (M1) — recorded 2026-09-29
Section titled “Version pins (M1) — recorded 2026-09-29”| Tool / package | Pin | Notes |
|---|---|---|
| AlgoKit CLI | 2.10.2 | pipx on Python 3.12.14 (D-046); never 2.11.0-beta |
| Python | 3.12.14 (uv-managed) | D-046 |
| uv / pipx / Poetry | 0.12.20 / 1.17.7 / 2.5.1 | tooling only |
| dev tools (poetry.lock, 2026-09-29) | python-dotenv 1.2.3, hypothesis 6.168.3, black 26.5.1, ruff 0.16.9, mypy 2.1.0 (pinned by puyapy), pytest 9.1.1, pytest-cov 7.1.0, pip-audit 2.10.1, pre-commit 4.6.2 | exact pins in pyproject.toml |
| puyapy | 5.10.1 | requires algorand-python 4.0.x |
| algorand-python | 4.0.0 | |
| algorand-python-testing | 1.1.0 | compatibility with algopy 4.0.0 checked by the bootstrap smoke test (D-028); never 1.2.0-beta |
| algokit-utils (py) | 4.2.3 | never 4.2.4-beta / 5.0.0-beta |
| algokit-client-generator (py) | 2.2.0 | |
| py-algorand-sdk | 2.12.0 | Falcon1024AlgorandSigner in algosdk/signer.py |
| algorand-falcon | 0.1.0 | win_amd64 wheel |
| algokit-python-template | 1.8.1 | _commit in projects/contracts/.copier-answers.yml |
| puyapy –target-avm-version | 11 (compiler default; choices 10-13) | read at bootstrap 2026-09-29; unchanged (no AVM 12/13 opcode needed in M1) |
| LocalNet algod image | algorand/algod:5.0.2-stable@sha256:3a2c9266b61e414e770aa69bd3d3867f8b350177c7af31df251db36ef70405a4 |
D-029; consensus V42 (268b63433a907455d439995bf916f6b296018f4f) |
| Docker Desktop | 4.93.0 (engine 29.8.1, Compose v5.5.1, WSL 2.7.14) | auto-update off; recorded 2026-09-29 |
| Node (M1 CID script) | 26.5.1 (host) | Node 24 LTS for M3/M4 |
| ipfs-unixfs-importer / multiformats / blockstore-core | 17.1.1 / 14.0.5 / 7.0.1 | spec/fixtures/package.json + package-lock.json |
| M3 (package.json, 2026-09-30) | Node 26.5.1 (engines >= 24) with node:sqlite (SQLite 3.53.3) · algosdk 3.8.0 · @algorandfoundation/algokit-utils 9.2.2 · @algorandfoundation/algokit-subscriber 3.4.0 · multiformats 14.0.5 · ipfs-unixfs-importer 17.1.1 · ipfs-unixfs-exporter 16.2.3 · blockstore-core 7.0.1 · @ipld/car 5.4.7 · @ipld/dag-pb 4.2.0 · typescript 5.9.3 · @types/node 26.6.3 · prettier 3.9.9 | D-075; better-sqlite3 dropped |
| M4 (for reference) | algosdk 3.8.0 · @algorandfoundation/algokit-utils 9.2.2 · algokit-subscriber 3.4.0 · algokit-client-generator 6.0.1 · next 16.3.6 · react 19.3.0 · typescript 5.9.3 · tailwindcss 4.3.3 · @txnlab/use-wallet 5.0.1 · better-sqlite3 13.0.3 · react-markdown 10.1.0 · remark-math 6.0.0 · rehype-katex 7.0.1 · rehype-sanitize 6.0.0 · katex 0.16.47 · @ipld/car 5.4.7 · pinata 2.5.6 · kubo 0.43.1 | re-verify at the M3 scaffold; web3.storage removed (D-026) |
Host facts (2026-09-29)
Section titled “Host facts (2026-09-29)”Windows 10 Pro 22H2 (build 19045), AMD Ryzen 5 2600, 16 GB RAM, ~32 GB free on C:. CPU virtualization disabled in firmware at start (BIOS SVM to enable), WSL not installed, no Docker. Python 3.14.5 (py default) and 3.8.5 (python on PATH) pre-existing; Node 26.5.1, npm 11.17.0, git 2.54. The project folder held only ARIADNE_SPEC.md and was not a git repository.
Open questions (mirror of §22 plus new)
Section titled “Open questions (mirror of §22 plus new)”- Refund of MBR overpayment (M1 keeps it, D-001).
FLAG_THRESHOLDabsolute vs relative (M2).- Whether
disputedshould also blocksubmit_review(closed for M2 by D-020: allowed). - Multi-author allocation rule (§10.2) — closed on 2026-09-29 by the owner: equal split, remainder to the submitter (D-054).
- Whether a review should stop earning once its reviewer becomes an accepted co-author (D-066, owner).
GRANT_COMMENT = 1/5grants nothing for voters of weight below 5; tune with real activity (D-057, owner).- Batch claiming across articles (M5; at most 5 articles per app call because of the 1 024-byte log budget).
- MainNet articles referencing TestNet ones (no; networks independent).
- Second pinning provider (M5): Filebase CAR import (unverified) / Filecoin Pin / self-hosted Kubo; Path A default = Pinata paid plan.
- Timing of the governance migration to native PQ multisig (Algorand roadmap: end of 2026).
asset_namecarrying the sequence (D-030): only if the measured publish budget stays <= 600 ops with itoa.Follows that work on every device (D-104 keeps them per browser): contract event, contract box or signed list; to decide before the MainNet contract.Decided: contract event, D-109.- ARC-19 vs ARC-89 for the current-version pointer (M5).
- Hosting for indexer / IPFS gateway / web (decide during M2, provision in M3; default one Linux VPS).
- Domain and EU trademark check (owner task, preamble of the spec).
- Constants tuning (§5) implies a redeploy of the immutable app: TestNet redeploy cadence to plan in M4.
D-048 Emulator (algorand-python-testing 1.1.0) limits observed at CP2 (amends D-039)
Section titled “D-048 Emulator (algorand-python-testing 1.1.0) limits observed at CP2 (amends D-039)”- Date: 2026-09-29 · Status: accepted
- Observed while making Layer A green (186 tests): (1) the emulator does NOT roll back state when a call fails (the AVM rejects the whole transaction), so negative tests use fresh ids instead of asserting box absence after a failure; (2)
arc4.Struct(kw_only=True)breaks the emulator’sfrom_bytes(positional constructor), so structs are plainarc4.Structand are still constructed with keyword arguments; (3)arc4.UIntN.nativeis deprecated in the emulator in favour ofas_uint64(), which the algorand-python 4.0.0 stubs also expose; the contract usesas_uint64()for every UIntN read (Address/Bool/String keep.native); (4)txn.logs(i)returns rawbytes; (5)BoxMap.maybe()on struct values cannot be tuple-unpacked in Puya 5.10.1 (in+[]used instead); (6)Account(...)in the emulator takesstr | algopy.Bytes, never rawbytes. - Consequences: documented in tests/README.md; every item above is either a Layer B assertion or a coding convention.
- Spec refs: §21 · Tests: tests/unit/, tests/static/
D-049 TEAL static analysis: tealer update/delete detectors are false positives on the Puya router
Section titled “D-049 TEAL static analysis: tealer update/delete detectors are false positives on the Puya router”- Date: 2026-09-29 · Status: accepted
- Context:
algokit task analyze(tealer 0.1.2) reports is-updatable, is-deletable and unprotected-* on both programs. The clear program ispushint 1; return(it never decides anything: update/delete are governed by the approval program). The approval program starts withtxn OnCompletion; !; assertbefore any branch, so UpdateApplication (4) and DeleteApplication (5) are rejected on every path; tealer does not recognise that idiom and only looks for explicit comparisons. - Decision: the audit-teal task analyses the approval program only and excludes the four update/delete detectors plus the LogicSig-oriented ones (rekey-to, missing-fee-check, can-close-asset, can-close-account). Immutability is proven instead by tests/static/test_teal_router.py (the NoOp gate precedes every branch and is the only OnCompletion handling), tests/static/test_arc56.py (no create/update/delete actions besides
create) and Layer B SEC-04/05 (update and delete transactions from deployer, governance and stranger are rejected on LocalNet).ci-teal-diffstill guards the artefacts. - Spec refs: §12.1, §20 Security, §21 · Tests: test_teal_router, test_arc56, SEC-04/05
D-050 Dev-mode timestamp offset is a per-block delta (amends D-019)
Section titled “D-050 Dev-mode timestamp offset is a per-block delta (amends D-019)”- Date: 2026-09-29 · Status: accepted (measured on LocalNet 5.0.2)
- Context: D-019 assumed a cumulative offset relative to the wall clock. Measured:
GET /v2/devmode/blocks/offsetanswers 404 until an offset is set; afterset_timestamp_offset(n)every new block’s timestamp is the PREVIOUS block’s timestamp + n (n = 100 gave +100, +100; n = 1 gave +1, +1).Global.latest_timestampinside a call is the timestamp of the previous block. - Decision:
time_travel(seconds)= set offset toseconds, produce one filler block, set offset to 1. From the first time travel on, ledger time advances one second per block: monotone and deterministic. A fresh LocalNet (down -v) is used for every acceptance run. - Spec refs: §5, §10.2, §20 · Tests: test_00_pq_spike (g), CAP-03, INV22-01
D-051 Every off-chain-built transaction carries a unique note
Section titled “D-051 Every off-chain-built transaction carries a unique note”- Date: 2026-09-29 · Status: accepted
- Context: algokit-utils caches suggested params for a few seconds, so two otherwise identical transactions built in that window share first/last valid and therefore the transaction id; the second is rejected with “transaction already in ledger”. It happened in the taxonomy seeding (two field names of equal length give two identical payments in one group).
- Decision: the deploy script notes each payment with the field id; the test harness notes every payment, asset transaction and app call with a session-random counter. The web app and CLI (M4/M5) must do the same for standalone repeated transactions.
- Spec refs: §12, §13 · Tests: deploy seeding, tests/localnet/conftest.py
unique_note
D-052 Deploy reuses an app only when genesis and governance both match
Section titled “D-052 Deploy reuses an app only when genesis and governance both match”- Date: 2026-09-29 · Status: accepted (amends D-038)
- Decision:
deploy_appreusesspec/networks.json[network].appIdonly if the stored genesis hash equals the node’s AND the app’s on-chaingovernanceequals the requested governance address; otherwise it creates a new application. Verified:algokit project deploy localnettwice on a fresh LocalNet -> first run creates, funds with exactly 100 000 microAlgo and seeds 42 fields in 6 Falcon-signed groups; second run reuses the app and adds nothing. The localnet entry of the manifest is machine-specific and is restored before committing. - Spec refs: §3.2, §12.1, §23 · Tests: GOV-04, idempotence step of the end-to-end run
D-053 Outcome of the PQ spike (closes D-041, verifies D-045)
Section titled “D-053 Outcome of the PQ spike (closes D-041, verifies D-045)”- Date: 2026-09-29 · Status: accepted
- Measured on LocalNet algod 5.0.2 (consensus 268b63433a907455d439995bf916f6b296018f4f):
- signer class in py-algorand-sdk 2.12.0 is
Falcon1024AlgorandSigner; public key 1 793 bytes;algokey pq check-addressinside the algod container confirms the governance address. - a pqsig payment at 3 x min fee confirms; at 1 x min fee the node rejects it.
- simulate with a scheme-only placeholder
PQSig(b"f1", 0, b"", b"")reportsgroup-usage3 000 000, identical to the Falcon-signed transaction (D-045 holds): fees can be sized before signing. - chosen path (D-041 candidate 1): typed-client composer with the Falcon signer registered through
AccountManager.set_signerandstatic_feefrom the usage formula; contingencies 2 and 3 were not needed. - caveat: never pass the Falcon signer as the explicit
signer=ofalgorand.send.*parameters. algokit-utils 4.2.3 treats any object withaddressandsignerattributes as an account and unwraps.signer, which onPQAlgorandSigneris the raw signing callable (“‘function’ object has no attribute ‘sign_transactions’”). Registered signers and explicitTransactionWithSignerobjects work. - every governance call of the suite (create-time seeding, add_field, set_governance round trips) is pqsig-signed; authors and voters with Falcon accounts publish, claim, vote and claim reputation (COST-08).
- signer class in py-algorand-sdk 2.12.0 is
- Spec refs: §12.1, §13.2, §20 Security · Tests: test_00_pq_spike, test_10_governance, test_90_measure
M2 decisions (community layer) — recorded 2026-09-29, before any M2 code
Section titled “M2 decisions (community layer) — recorded 2026-09-29, before any M2 code”D-054 Multi-author allocation: equal split, remainder to the submitter (OWNER decision; closes the §10.2 M2 gate)
Section titled “D-054 Multi-author allocation: equal split, remainder to the submitter (OWNER decision; closes the §10.2 M2 gate)”- Date: 2026-09-29 · Status: accepted (owner chose candidate (b) of §10.2); the rule stands, its mechanics (accumulators, carry, baseline) are superseded by D-072
- Context: with N accepted authors the text of v2.7 let every author claim the full
vote_total(N x inflation). The owner chose “equal split with the submitter taking the remainder”. Two things had to be fixed to make that rule implementable under the pull model: the granularity of the division and what happens when the author set changes. - Decision:
- The article box gains three fields (appended after
asa_id):share_total: uint64(cumulative equal share credited to EACH accepted author),split_carry: uint16(vote weight received but not yet divisible, always <author_count) andsubmitter_extra: uint64(remainders credited to the submitter). Article value = 210 bytes, box MBR 90 100, publish deposit 219 400 microAlgo. vote_article(weightw,N = author_countat vote time):pool = split_carry + w; q = pool / N; share_total += q; split_carry = pool - q x N. Constant cost, independent of N; still no reputation box is touched.accept_coauthorship: the open remainder belongs to the author set that earned it, so it is settled to the submitter before the set changes:submitter_extra += split_carry; split_carry = 0; thenauthor_count += 1and the new co-author’sclaimed_total := share_total(replaces “claimed_total = article.vote_total” of §7.6; same purpose: only votes cast after acceptance ever count).claim_reputation:entitled = share_totalfor a co-author andshare_total + submitter_extrafor the submitter;claimable = entitled - au.claimed_total(MUST be > 0); the rest of §10.2 is unchanged (daily cap, secondary = granted_primary / 2,claimed_total += granted_primary).- Conservation: at any time
vote_total = sum over periods (N x share increments) + submitter_extra + split_carry, so the reputation materialisable by all authors together never exceedsvote_total(no inflation) and at mostauthor_count - 1weight units are waiting insplit_carry.
- The article box gains three fields (appended after
- Rejected alternative: dividing every single vote (
floor(w / N)each,w mod Nto the submitter). It is simpler, but most votes of a young network weigh 1 to 3, so with 2-3 authors nearly everything would go to the submitter and co-authors would receive nothing: that is not an equal split. The cumulative division with a carry is still pure uint64 arithmetic and deterministic from the events (Voted.weightandCoauthorAccepted.author_count). - Rejected alternative: letting the submitter claim the open
split_carryat any time. It makes the split depend on how often the submitter claims (claiming after every weight-1 vote would starve the co-authors). The submitter receives the remainder only when the author set changes; while the set is stable the carry is shared equally by the next votes. - Consequences: invariants 8 and 9 of §10.3 hold (
claimed_total <= share_total + submitter_extra <= vote_total; a late co-author starts at the currentshare_totaland the older carry is settled away from them). Single-author articles behave exactly as in M1 (N = 1givesq = w, carry 0). §6.4, §7.6, §8.0, §10.2, §13.3 amended in the spec copy. - Spec refs: §6.4, §7.6, §8.0, §10.2, §10.3 inv 8-9, §17 inv 27 · Tests: SPLIT-01..06 (tests/ref/test_ref_m2.py), SPLIT-A-01..04 (Layer A property tests), COAU-01..42, COAU-B-01..07 (tests/localnet/test_55_coauthors.py)
D-055 M2 method surface, payments and inner transactions
Section titled “D-055 M2 method surface, payments and inner transactions”- Date: 2026-09-29 · Status: accepted
- Decision: methods that create a box take the leading
payargument of D-001:invite_coauthor(pay, article_id, coauthor),submit_review(pay, article_id, cid, recommendation) -> uint64,vote_review(pay, article_id, review_seq),comment(pay, article_id, cid, reply_to, mentions) -> uint64,vote_comment(pay, article_id, comment_seq),flag(pay, article_id, reason). Methods that create nothing take no payment:accept_coauthorship(article_id),resolve_comment(article_id, comment_seq),resolve_dispute(article_id, outcome). Onlyresolve_dispute(retracted)submits an inner transaction (the ARC-71 Revoked AssetConfig of D-033, fee pooled by governance). - Status columns (§7.4 with D-020): invite/accept {preprint, under_review}; submit_review and flag {preprint, under_review, final, disputed}; comment every status; vote_review, vote_comment and resolve_comment follow the vote_article column {preprint, under_review, final}; resolve_dispute only in disputed.
invite_coauthoraccepts any address except the submitter (an invitation nobody can accept is inert and is paid by the submitter);invited_countandauthor_countoverflow (65 535) fail.- Spec refs: §7.4, §7.6, §12 · Tests: PERM-01..05 (permission matrix, 13 methods x 5 statuses), COAU-04, COAU-11, REV-08, REV-21, CMT-02, CMT-22, RES-02, tests/static/test_arc56.py
D-056 M2 boxes
Section titled “D-056 M2 boxes”- Date: 2026-09-29 · Status: accepted
- Decision:
Review(reviewer, cid, recommendation, created_at, useful_total) = 85 bytes underr; reviewer index underriholds the raw 8-bytereview_seq;Comment(author, cid, reply_to, root_seq, depth, created_at, vote_total, reply_count, resolved, resolved_at) = 118 bytes underc; review and comment votes reuseVote(24 bytes) undervr/vc;Flag(reason, weight, created_at) = 17 bytes underf. Deposits: invite 29 300; review 43 300 + index 22 500 (+ 25 300 for the reviewer’s first reputation box in that field); comment 56 500 (+ 25 300 likewise); review/comment vote 32 100; flag 26 500. - Spec refs: §6.2, §6.4, §10.5, §13.3 · Tests: tests/ref/test_ref.py, tests/static/test_arc56.py, Layer B measurements
D-057 Pushed grants (review useful, comment upvote, comment resolved)
Section titled “D-057 Pushed grants (review useful, comment upvote, comment resolved)”- Date: 2026-09-29 · Status: accepted
- Decision:
raw=w x 3 / 1(review; 0 when the voter is an accepted author),w x 1 / 5(comment) or 5 (resolution);granted = min(raw, cap_remaining(recipient, primary field)). The recipient’s reputation box is read only whenraw > 0and MUST exist (the call fails otherwise, §10.5); it is written, andReputationChangedemitted, only whengranted > 0(D-006). The vote box storesweightandgranted;review.useful_total/comment.vote_totalalways grow by the full weight. Event order: the canonical event first, then at most oneReputationChanged. - Observation for the owner (constants are tunable per §5): with
GRANT_COMMENT = 1/5a comment upvote from a voter of weight below 5 (reputation below 16) grants 0; early on, comments earn reputation mainly through resolution. - Accepted authors may comment and may vote on comments; an accepted author’s comment can be upvoted but never resolved (§8.3).
- Spec refs: §8.2, §8.3, §10.2, §10.5 · Tests: REV-15..38, CMT-20..30, RES-01..12, CMT-41, REV-B-02..05, CMT-B-04..05
D-058 Flags and disputes
Section titled “D-058 Flags and disputes”- Date: 2026-09-29 · Status: accepted
- Decision:
flagrequires an existing reputation box of the flagger in the article’s primary field withrep >= MIN_FLAG_REP; weight1 + isqrt(rep); one flag box per (article, dispute_round, flagger).round_flag_weightalways accumulates (D-020); the transition todisputedhappens in the call that makes it reachFLAG_THRESHOLDwhile the status is preprint, under_review or final, storingstatus_before_dispute.Flaggedcarriesround_totalandnew_status(the status after the call), so no separateStatusChangedis emitted (one canonical event per call).resolve_dispute(governance, status = disputed, outcome 1 cleared | 2 retracted): cleared restoresstatus_before_dispute; retracted revokes the ASA exactly like an author retraction (D-033); both then setdispute_round += 1(fails at 65 535),round_flag_weight = 0,status_before_dispute = 0.DisputeResolved.dispute_roundis the round that was resolved. - Spec refs: §7.3 rows 6-8, §9, §11 · Tests: FLAG-01..23, DISP-01..17, PERM-01..05, ST-04 = FLAG-B-01, DISP-B-01..03 (tests/localnet/test_66_flags_disputes.py)
D-059 Comments, threads and mentions
Section titled “D-059 Comments, threads and mentions”- Date: 2026-09-29 · Status: accepted
- Decision:
reply_tois 0 or an existing comment of the same article (the key contains the article id, so a comment of another article can never match);depth = parent.depth + 1 <= MAX_COMMENT_DEPTH,root_seq = parent.root_seq,parent.reply_count += 1; a top-level comment hasroot_seq= its own sequence.mentionsholds 0 to 5 addresses, pairwise distinct and different from the sender; nothing else is checked about them. Review and comment CIDs MUST use codec 0x55 (raw). - Spec refs: §6.1, §8.3, §17 inv 25-26 · Tests: CMT-01..19, CMT-40, CMT-B-01..03
D-060 updated_at in M2
Section titled “D-060 updated_at in M2”- Date: 2026-09-29 · Status: accepted (refines D-006)
- Decision: the article box is written, and
updated_atstamped, only by calls that change an article field (invite, accept, submit_review, comment, flag, resolve_dispute and the M1 methods).vote_review,vote_commentandresolve_commentchange only the review / comment / vote / reputation boxes and do not write the article box. The indexer derives “last activity” from events. - Spec refs: §6.4 · Tests: REV-15, CMT-20, RES-01, REV-B-02, CMT-B-04
D-061 Mentions are passed in strictly ascending order (amends D-059)
Section titled “D-061 Mentions are passed in strictly ascending order (amends D-059)”- Date: 2026-09-29 · Status: accepted (measured on LocalNet)
- Context: the first implementation compared every pair of mentions. Measured on LocalNet 5.0.2, a reply with 5 mentions cost 653 opcodes (consensus limit 700, Layer C gate 600) before even creating the commenter’s reputation box; pairwise distinctness is quadratic.
- Decision:
comment()requiresmentionssorted by the raw 32-byte address, strictly ascending (compared as big-endian unsigned integers, AVMb<). Strict order implies pairwise distinctness, so the MUST of section 8.3 (“distinct, never the sender, at most MAX_MENTIONS”) is enforced in one pass. The order carries no meaning; clients sort before sending (one line in the web app and the CLI). An unsorted or duplicated list fails with “mentions must be distinct and in ascending order”. - Consequences: section 8.3 and section 12 annotated in the spec copy; the indexer must not assume that the order of
Commented.mentionsreflects the order in the comment body. - Spec refs: §8.3, §11, §17 inv 26, §21 C · Tests: CMT-mentions matrix (Layer A), test_64_comments (Layer B), SIM-05
D-062 Reputation helpers read each box once (no behaviour change)
Section titled “D-062 Reputation helpers read each box once (no behaviour change)”- Date: 2026-09-29 · Status: accepted (measured on LocalNet)
- Context: with the M2 entitlement branch
claim_reputationreached 577 opcodes (545 in M1). - Decision:
_credit(key, raw, ts)reads the reputation box once, applies the saturating daily cap and writes only when the grant is positive; events are emitted by the caller after every write and after the MBR check, in the normative order (canonical event first)._pushwraps it for review / comment grants (box read only whenraw > 0, MUST exist). - Spec refs: §10.2, §10.5, §21 C · Tests: REP-01 property test, CLAIM-, REV-, CMT-*, Layer C gate
D-063 Deploy reuses an app only when the program matches too (amends D-052)
Section titled “D-063 Deploy reuses an app only when the program matches too (amends D-052)”- Date: 2026-09-29 · Status: accepted
- Context: the application is immutable, so a rebuilt contract can never be installed into an existing app id; D-052 compared only genesis and governance and would have reused an app running an older program.
- Decision:
deploy_appreusesnetworks.json[network].appIdonly if the genesis hash matches, the on-chain approval program equalsbyteCode.approvalof the committed ARC-56 artefact, and the on-chain governance equals the requested one. Otherwise it creates a new application. The M2 program needs 2 extra program pages; algokit-utils computes them at creation. - Spec refs: §3.2, §12.1 · Tests: GOV-04, idempotence step of the end-to-end run
D-064 Helpers are inlined (@subroutine(inline=True))
Section titled “D-064 Helpers are inlined (@subroutine(inline=True))”- Date: 2026-09-29 · Status: accepted (measured on LocalNet 5.0.2)
- Context: opcode budget is per application call (700); program size is per application (8 192 bytes). Measured with called subroutines: claim_reputation 589, comment (reply, 5 mentions, first reputation box) 505, approval program 4 902 bytes. Measured with every helper inlined: claim_reputation 474, comment 479, vote_review 344, publish 312, approval program 6 066 bytes (3 pages).
- Decision: every helper of
contract.py,keys.pyandcid.pyis declared@subroutine(inline=True). The budget margin (at least 120 opcodes under the Layer C gate for every method) is worth 1 164 bytes; 2 122 bytes of program space remain.tests/static/test_arc56.pyfails the build if the program exceeds 8 192 bytes; the Layer C gate fails any call above 600 opcodes. - puyapy optimisation level 2 was measured too: 4 929 vs 4 940 bytes, no meaningful change; the compiler default (level 1) is kept.
- Spec refs: §21 C, §23.5 · Tests: SIM-05 (tests/localnet/test_92_measure_m2.py), test_program_fits_the_consensus_limit
D-065 The last dispute round takes no flags (amends D-058)
Section titled “D-065 The last dispute round takes no flags (amends D-058)”- Date: 2026-09-29 · Status: accepted
- Context:
resolve_disputefails whendispute_roundis 65 535 because the counter cannot be incremented (D-016). Flags were still accepted in that round, so a dispute opened there could never be resolved and the article would staydisputedfor ever. Found by the independent Layer A tests. - Decision:
flagfails with “dispute round overflow” whendispute_roundis 65 535. An article can therefore go through at most 65 534 disputes and can never be stuck indisputed. The guard inresolve_disputestays as defence in depth. - Spec refs: §9, §17 inv 15-16 · Tests: DISP-12, DISP-12b, DISP-13
D-066 Readings of section 8 confirmed by the M2 tests (no contract change)
Section titled “D-066 Readings of section 8 confirmed by the M2 tests (no contract change)”- Date: 2026-09-29 · Status: accepted; the first item is flagged to the owner as a possible later rule
- A reviewer who accepts co-authorship AFTER reviewing keeps the review and keeps receiving grants from the useful votes of non-authors (section 8.0: earlier actions “remain on record and keep their effects”); from then on that person’s own votes on reviews of the article grant 0. Known consequence: a submitter can invite a friendly reviewer as co-author without stopping that review from earning. Closing it would need a rule such as “a review stops earning once its reviewer is an accepted author”, which is a protocol change for the owner to decide (not improvised here, section 10.4).
- A resolved comment can still be voted and replied to (section 8.2 only requires voter != comment.author; section 8.3 allows replies to resolved comments).
- Review and comment CIDs need not be unique; replying to one’s own comment is an ordinary reply.
- When a mention list is both out of order and contains the sender, either error may be reported; clients must not rely on which one.
- Spec refs: §8.0, §8.1, §8.2, §8.3 · Tests: REV-07, REV-27, RES-06, RES-11, CMT-05, CMT-07
D-067 Suggested-params cache of algokit-utils is switched off (amends D-051)
Section titled “D-067 Suggested-params cache of algokit-utils is switched off (amends D-051)”- Date: 2026-09-29 · Status: accepted (observed on LocalNet)
- Context: algokit-utils 4.2.3 (Python) keeps the suggested transaction parameters for
time.time() + 3_000, i.e. 3 000 seconds, although the value is documented as milliseconds (“three seconds”). In dev mode every transaction is a block, so after 1 000 blocks in one session every new transaction was built with a validity window that had already closed (“txn dead: round 1006 outside of 2–1002”). M1 never reached 1 000 blocks in a session; the M1 + M2 suite does. - Decision: the LocalNet harness and the deploy script call
set_suggested_params_cache_timeout(0), so parameters are read from the node for every transaction. Unique notes (D-051) stay: two transactions built without a block in between still share their validity window. - Consequences: long-running Python tooling of later milestones must do the same; the web app uses the TypeScript library and must be checked separately in M4.
- Spec refs: §13.2 · Tests: the full tests/localnet run (more than 1 000 blocks)
D-068 The network manifest is written with retries
Section titled “D-068 The network manifest is written with retries”- Date: 2026-09-29 · Status: accepted (observed on this Windows host)
- Context: during the idempotence check the first
algokit project deploy localnetcreated and seeded the application and then failed withOSError: [Errno 22]while openingspec/networks.jsonfor writing (another process held the file for a moment). The application existed but was not recorded, so the next run created a second one. - Decision:
write_manifestretries up to 5 times with a growing pause; if every attempt fails it logs the entry that has to be recorded by hand and raises. Verified afterwards on a fresh LocalNet: first run creates app 1003, funds it with exactly 100 000 microAlgo and seeds 42 fields in 6 Falcon-signed groups; second run reuses the app and adds nothing. - Consequences: for TestNet / MainNet (M4) an unrecorded deployment costs real funds; the deploy log always prints the application id before the manifest is written.
- Spec refs: §3.2, §23 · Tests: idempotence step of the end-to-end run
D-069 Notes from the independent M2 test pass (no contract change)
Section titled “D-069 Notes from the independent M2 test pass (no contract change)”- Date: 2026-09-29 · Status: accepted
- D-044 applies to every accepted author: a co-author cannot claim while the article is disputed and loses the unclaimed share when it is retracted, like the submitter.
- Only the submitter controls the author list (section 7.6), so every acceptance dilutes the future shares of the existing co-authors, and every remainder goes to the submitter (D-054). Recorded for the owner; a co-author consents only to their own acceptance.
- When two preconditions fail at once the contract reports the first one it checks; clients must not depend on which message they get.
- Section 10.3 invariant 6 says “in M1” and section 17 invariant 12 says “M1/M2”: reputation never decreases in either milestone (tested in both).
- Spec refs: §7.4, §7.6, §10.3, §17 · Tests: COAU-20, COAU-39
D-070 Owner delegation after the M2 report (2026-09-29)
Section titled “D-070 Owner delegation after the M2 report (2026-09-29)”- Date: 2026-09-29 · Status: accepted (owner: “sobre las otras decisiones, lo que tú creas”)
- Context: the M2 summary listed the judgement calls taken during M2 and three open points. The owner delegated all of them except the co-author model, on which the owner made a proposal of their own (D-071).
- Decisions taken under that delegation:
- Mentions in ascending order (D-061) and no flags in the last dispute round (D-065): confirmed.
GRANT_COMMENTstays 1/5, the value of section 5. It is low on purpose (comments are cheap to write); the real grant for a useful comment is the resolution (5). Revisit with real activity; any change is a new deployment.- Hosting: M3 is developed and accepted locally (LocalNet + local SQLite), like M1 and M2. One Linux VPS (indexer + IPFS gateway + web) is provisioned when the first public deployment is prepared (M4); nothing has to be bought before that.
- Not decided here because they depend on D-071: the granularity of the split (D-054), the reviewer who later becomes a co-author (D-066) and the dilution of existing co-authors (D-069).
- Spec refs: §5, §8.3, §9, §22 · Tests: unchanged
D-071 Co-author model: author list fixed from the start (OWNER proposal, pending)
Section titled “D-071 Co-author model: author list fixed from the start (OWNER proposal, pending)”- Date: 2026-09-29 · Status: closed by D-072 (the owner chose option 1: “La 1”)
- Context: the owner asked whether it would be simpler to establish every co-author when the article is published, instead of inviting and accepting over time until
final. This touches the section 2 row “Multi-author” (invitations, signed acceptance, no limit but 65 535), so it can only change by an explicit owner decision, like D-023 and D-024. - Assessment: a list that never changes removes the accounting of D-054 (no carry, no baseline per co-author), makes the split independent of when each co-author signs (today a late signer loses the earlier votes, usually the most numerous), and removes the two loose ends of D-066 and D-069. Two constraints remain whatever the model:
- Consent (invariant 23): every co-author signs. The signatures cannot be part of the publish group in practice, because a transaction is valid for at most 1 000 rounds (about 45 minutes) and every co-author would have to sign inside that window. Co-authors can be declared at publication and confirm later.
- A list that is complete at the instant of publication cannot be unlimited: it has to fit one call (opcode budget, 1 024 bytes of logs). Estimate from the measured cost of the mention loop (about 29 opcodes per address): about 8 co-authors in a single call, about 25 with auxiliary transactions in the group. To be measured if chosen.
- Options:
- (1) List declared in
publishand closed for ever; each declared co-author confirms by signing when they can; every author receives an equal share of all votes since publication; an address that already voted on or reviewed the article cannot confirm. Capped number of co-authors. Reopens section 2 and changespublish. - (2) Invitations as today (no cap, submitter pays the boxes), but the list closes by itself at the first reputation claim or when the article leaves
preprint; same equal share of all votes. Section 2 stays as written;publishunchanged. - (3) Keep M2 as tagged.
- (1) List declared in
- Consequences: nothing is deployed on a public network, so this is the cheapest moment to change. Options 1 and 2 reopen the M2 acceptance (contract, oracles, tests, report, new tag); M3 waits for the choice because the indexer reads these events.
- Spec refs: §2, §7.4, §7.6, §8.0, §10.2, §10.3 inv 9, §17 inv 23, §18 · Tests: to be written with the chosen option
Co-author model v2 — recorded 2026-09-29, before the code change
Section titled “Co-author model v2 — recorded 2026-09-29, before the code change”D-072 The author list is declared in publish (OWNER decision; closes D-071; reopens §2 “Multi-author”)
Section titled “D-072 The author list is declared in publish (OWNER decision; closes D-071; reopens §2 “Multi-author”)”- Date: 2026-09-29 · Status: accepted (owner, option 1 of D-071)
- Decision:
publish(pay, cid, primary_field, secondary_field, article_type, parent_article, coauthors: address[]): the submitter declares 0 toMAX_COAUTHORS(25) co-authors. The list is strictly ascending by raw 32-byte address (hence distinct, as in D-061) and never contains the submitter.publishcreates one co-author box per declared address (accepted = false,invited_at = ts), paid by the submitter, and setsinvited_countto the number declared, which never changes.Publishedcarries the list. Nobody can be added later:invite_coauthorandCoauthorInvitedno longer exist.accept_coauthorship(article_id): a declared address confirms by signing, once, while the article is preprint, under_review or final (not in disputed, not in retracted). It MUST NOT have voted on the article nor reviewed it (no article-vote box, no reviewer index). It setsaccepted,accepted_atand incrementsauthor_count;claimed_totalstays 0.- Allocation (the owner’s rule of D-054, “equal split, remainder to the submitter”):
N = 1 + invited_countis fixed for ever. Every accepted co-author is entitled tovote_total / N(floor); the submitter to the ceiling ofvote_total / N.claimable = entitled - claimed_total; the rest of section 10.2 is unchanged. - Why the ceiling and not “floor plus the whole remainder”: the latter decreases when the total grows (N = 3: a total of 5 gives 3, a total of 6 gives 2), so reputation already claimed could exceed the votes received. The ceiling is the largest share for the submitter that never decreases and never creates reputation: the entitlements of all authors add up to at most
vote_total, short by at mostN - 1weight units that wait for the next votes. - The share of a declared co-author who never confirms is never claimed by anyone. Declaring somebody therefore costs every author a part of their share, which discourages padding the list.
- Self-action rules (section 8.0) are unchanged and bind ACCEPTED authors only. A declared address that has not confirmed may vote, review and flag like anybody else, so nobody can be silenced by being declared without consent; but after voting or reviewing it can never confirm, so nobody ever receives reputation from their own vote and no reviewer ever becomes an author of the article they reviewed.
- The article box returns to 192 bytes (
share_total,split_carry,submitter_extraare removed) andvote_articleis the M1 method again. Publish deposit = 212 200 + 29 300 per declared co-author (microAlgo).CoauthorAccepted.claimed_totalis kept in the event for compatibility with section 11 and is always 0.
- Consequences:
- Section 2 row “Multi-author” is amended by the owner: co-authors are declared at publication instead of invited over time, and the limit is 25 co-authors per article instead of 65 535. Signed acceptance, “only accepted co-authors count” and “editing rights stay with the submitter” stand.
- Section 7.4: the column “invite/accept co-author” becomes “accept co-authorship”, allowed in final too (the list is already fixed, confirming changes nothing for the others).
- Section 10.3 invariant 9 (“a co-author accepted after votes were cast can never claim those earlier votes”) is replaced by: the author list is fixed at publication, every accepted author is entitled to an equal share of all votes, and an address that voted on or reviewed the article can never become an accepted author.
- Section 18: “late co-author harvesting earlier votes” is now prevented by the closed list.
- D-066 first item (reviewer who later becomes a co-author) and D-069 second item (dilution of existing co-authors) no longer apply.
- The M2 acceptance is reopened: contract, oracles, tests, measurements and report are redone; tag
m2stays as history and the result is taggedm2.1.
- Spec refs: §2, §6.4, §7.1, §7.4, §7.6, §8.0, §10.2, §10.3, §11, §12, §13.3, §17, §18 · Tests: SPLIT-01..09 (tests/ref/test_ref_m2.py), COAU-01..50, SPLIT-A-01..06 (property), REV-39, REV-40, FLAG-22, FLAG-23, CMT-31, COAU-B-01..07 (tests/localnet/test_55_coauthors.py)
D-073 extend() lends budget and box references; the Layer C gate counts application calls
Section titled “D-073 extend() lends budget and box references; the Layer C gate counts application calls”- Date: 2026-09-29 · Status: accepted
- Context: one application call may touch 8 boxes and spend 700 opcodes.
publishtouches 3 to 5 boxes plus one per declared co-author. Box references and opcode budget are shared by the application calls of a group. - Decision:
extend()is an ABI method that changes nothing and emits nothing. A client adds to the publish group as manyextendcalls as the box references require:ceil(boxes / 8) - 1; a publication with few co-authors needs none. Everyextendcall carries a distinct note (D-051). The Layer C gate becomes: opcodes <= 600 x application calls of the group and box references <= 8 x application calls; the log limits are unchanged (32 entries, 1 024 bytes per call), and they are what caps the list:Publishedwith 25 co-authors is 914 bytes. - Consequences: a publication that needs more than one call’s budget is flagged in the report (section 21 C). Invariant 21 is unaffected:
extendis not a state-changing call. - Spec refs: §12, §13.2, §21 C · Tests: tests/static/test_arc56.py, COAU-B-*, SIM-05
D-074 Outcome of the co-author model on LocalNet (verifies D-072 and D-073)
Section titled “D-074 Outcome of the co-author model on LocalNet (verifies D-072 and D-073)”- Date: 2026-09-29 · Status: accepted (measured on LocalNet algod 5.0.2)
- Measured, Ed25519 and pqsig alike: publish with 0 / 1 / 2 / 3 / 4 / 9 / 25 co-authors costs at most 370 / 401 / 442 / 483 / 524 / 741 / 1 421 opcodes (about 41 per declared co-author) in 1 / 1 / 1 / 1 / 1 / 2 / 4 application calls, with at most 5 / 5 / 6 / 7 / 8 / 13 / 29 box references; the
Publishedlog with 25 co-authors is 914 bytes (926 with the return value, limit 1 024). Deposits equal the min-balance delta to the microAlgo: 212 200 + 29 300 per co-author (944 700 with 25). accept_coauthorship 144 opcodes, 4 box references, no deposit. claim_reputation 479, vote_article 189. Program 5 932 bytes of 8 192. MAX_COAUTHORS = 25is confirmed: the log budget of one call would allow 28, the round figure leaves room for the return value and is easy to state to users.- Rule for clients, verified by COAU-B-06: boxes = 3 + (1 with a secondary field) + (1 for an amendment) + co-authors;
extendcalls = ceil(boxes / 8) - 1. A publication without theextendcalls it needs is rejected. - Known residual, left as it is: a declared co-author can mark a review of the article useful before confirming, and that vote grants normally (the rule of section 8.2 binds accepted authors). Blocking it would let a submitter cancel somebody’s review votes just by declaring them without consent, and it would protect nothing: any other address the submitter controls can cast the same vote (section 10.4, not Sybil-resistant). Found by the independent review tests (REV-27).
- Test harness:
docker compose up --waitreturns a few seconds before algod answers, and a suite started right after it was skipped as a whole once; thealgorandfixture now waits up to 30 seconds for the node before skipping. A later run lost the node in the middle (66 passed, then connection errors; the container was found exited with code 143, i.e. it had received a stop signal); the whole suite was repeated on a fresh LocalNet. - Acceptance of the new model: 74 LocalNet tests passed on a fresh LocalNet, deploy idempotence verified again (first run creates the app and seeds 42 fields, second run reuses it); offline figures in M2_REPORT.md.
- Notes from the independent test pass of the new model (no contract defect found): votes on reviews and on comments, flags, comments and resolutions do not block a confirmation, only an article vote or a review does;
CoauthorAccepted.author_countis the number of confirmed authors after that signature and must not be read as N; when two preconditions fail at once the first one checked is reported (D-069). The authors of the offline tests reported mutation checks in scratch directories for the new model: 109 broken copies of the contract (42 reviews, 38 co-authors and split, 29 flags and disputes), all detected by the complete test files; single property tests run alone are a random second layer and missed a few mutants in some runs. - Spec refs: §7.6, §10.2, §12, §13.3, §21 C · Tests: tests/localnet/test_55_coauthors.py, test_92_measure_m2.py, tests/unit/test_coauthors.py, test_split_property.py
M3 decisions (indexer) — recorded 2026-09-30, before the code
Section titled “M3 decisions (indexer) — recorded 2026-09-30, before the code”D-075 Indexer runtime: Node with the built-in SQLite, no native modules, no build step
Section titled “D-075 Indexer runtime: Node with the built-in SQLite, no native modules, no build step”- Date: 2026-09-30 · Status: accepted
- Context: the plan pinned better-sqlite3 13.0.3 and Node 24 LTS. The host runs Node 26.5.1, whose built-in
node:sqlite(SQLite 3.53.3) offers the math functions the ranking needs (pow) and JSON functions; better-sqlite3 is a native module that would have to be compiled for this Node line (Visual Studio build tools on this host, another toolchain on the VPS). - Decision: the indexer uses
node:sqlite(DatabaseSync), synchronous like better-sqlite3 was meant to be. It runs TypeScript directly through Node’s type stripping (erasableSyntaxOnly,.tsimports), so there is no build step;tsc --noEmittype-checks,node --testruns the tests, prettier formats. The REST API is served withnode:httpand a small router (no web framework).engines.node >= 24; developed and accepted on 26.5.1; the VPS of M4 installs the current LTS. - Pins (package.json, exact): algosdk 3.8.0, @algorandfoundation/algokit-utils 9.2.2, @algorandfoundation/algokit-subscriber 3.4.0, multiformats 14.0.5, ipfs-unixfs-importer 17.1.1, ipfs-unixfs-exporter 16.2.3, blockstore-core 7.0.1, @ipld/car 5.4.7, @ipld/dag-pb 4.2.0; dev: typescript 5.9.3, @types/node 26.6.3, prettier 3.9.9. better-sqlite3 is dropped from the M3 pin list.
- Spec refs: §3, §15 · Tests: projects/indexer/test
D-076 Indexer data model and ingestion rules
Section titled “D-076 Indexer data model and ingestion rules”- Date: 2026-09-30 · Status: accepted
chain_events(tx_id, log_index, round, intra, app_id, event_name, event_version, payload_json, ts), primary key(tx_id, log_index)(D-037: one application call emits up to three events); ingestion is an INSERT OR IGNORE, so a replay is idempotent.watermark(app_id, last_round)is written in the same transaction as the events of a round.- algokit-subscriber only fetches the application calls (filter
appId+type: appl,sync-oldest, watermark); the raw logs are decoded bysrc/events.ts, a port of the Python oracletests/ref/events.pyfrom the same ARC-56eventslist (selector = sha512/256 of the signature, ARC-4 tuple payload, return log151f7c75skipped), proven equal to the oracle on exported vectors (test/fixtures/events.json). The decoded arguments are stored by name inpayload_jsonwith addresses as text, byte arrays as hex and integers as numbers (all fit in 53 bits excepttsand totals, which are stored as strings when aboveNumber.MAX_SAFE_INTEGER; in practice never). (amended 2026-09-30: the subscriber’s ownarc28Eventsdecoding was not used, so that the decoder is one code path with the oracle.) - Projections are a port of
projects/contracts/tests/ref/replay.py, event by event, inside one SQLite transaction per round; every projection row can be traced to achain_eventsrow through(tx_id, log_index)columns where the spec asks for it (versions, votes, flags, disputes, mentions, notifications).rebuilddrops the projection tables and re-applieschain_eventsfrom the first row; the result must be byte-identical to incremental ingestion (tested). - Derived, never stored on-chain: entitlement and claimable per (article, author) from
vote_total,invited_countand the sum ofReputationClaimed.consumed(D-072); rankingscore = vote_total / (age_days + 2) ** 0.8computed at query time. - One process per network: it reads
spec/networks.json, refuses to start if algod’s genesis hash differs from the manifest entry, and every API response carriesnetworkandappId(section 15). - Content (section 14.3): a
contentrow per distinct article CID; aContentSourcefetches a directory either through a trustless gateway (CAR, every block hash verified, the directory re-hashed with the canonical importer and compared with the CID) or from a local directory (tests, LocalNet without IPFS);validation_statusisvalid,mismatch,malformed,unavailableoroversized;authors[]beyond the first only produces a notice. When the manifest has no gateway, content staysunavailableand articles are served with that flag, never hidden. - Notifications are derived from events:
reply,mention,comment_on_my_article,review_on_my_article,comment_resolved,coauthor_invite(fromPublished.coauthors); read state is a local column. - Spec refs: §14.3, §15, §16.1 · Tests: projects/indexer/test (decoder parity with the Python oracle, reducer scenario, rebuild equality, API, content, LocalNet catch-up compared with the boxes)
D-077 The sample article’s placeholder author was not an address (fixture corrected, CID changed)
Section titled “D-077 The sample article’s placeholder author was not an address (fixture corrected, CID changed)”- Date: 2026-09-30 · Status: accepted
- Context: the indexer’s validation (§14.3) compares
authors[0]ofindex.mdwith the on-chain author.spec/fixtures/sample-article/index.mdcarried a 90-character placeholder (84A+Y5HFKQ) that is not a valid Algorand address; the zero address is 58 characters (52A+Y5HFKQ). The same string appeared in a deadorbranch oftests/localnet/test_20_publish.py. Nothing on-chain depends on the front-matter, so the contract tagsm1/m2.1are unaffected, but the fixture CID changes with its bytes. - Decision:
authors[0]of the fixture is now the zero address;expected-cid.jsonwas regenerated twice withspec/fixtures/compute-cid.mjs(identical runs):bafybeidzsrohfscy7xlt4dh53j3kmlxunjl3r2354pvtfn6pysnjpmduna, bytes0170122079945c72c858fdd73e0cfdda76a62ef46a57b8eb7de3eb32b7cfc49a97b07468, unixfs size 2 020. References updated:tests/ref/test_ref.py(CID-08),spec/publishing-guide.md, the dead branch intest_20_publish.py. The indexer re-hash reproduces the new CID (test/content.test.ts), and the fixture validates asvalidagainst on-chain metadata(author = zero address, field 0x0303, secondary 0x0501, type article, parent 0). - Consequences: any earlier LocalNet database that published the old fixture CID simply holds a different CID; no migration. The old CID
bafybeihtc64snftpqcog5wnwxc3hq5t6jivumtlpvorcsxnemkyxyzpuvyis retired. - Spec refs: §14.2, §14.3, §14.5 · Tests: tests/ref/test_ref.py CID-08, tests/static/test_fixture_cid.py, projects/indexer/test/content.test.ts
D-078 Indexer API conventions and M3 acceptance
Section titled “D-078 Indexer API conventions and M3 acceptance”- Date: 2026-09-30 · Status: accepted
- API (§15) judgement calls: read-only
GETovernode:http, JSON, CORS*(the web app of M4 runs on another origin); every response, including errors, carriesnetworkandappId.GET /articlesdefaults tosort=score,limit=20(max 100) and excludesretractedunlessstatus=is given;field=matches the primary or the secondary field (an article listed under its secondary field is part of that field’s feed);type=andstatus=accept the number or the name. Each article carrieswarnings[](disputed,retracted,content:<status>when the content is notvalid) and the content summary (title, abstract, keywords) joined by CID.GET /articles/:idreturns authors (submitter first, then co-authors by address) withentitledandclaimable(D-072 arithmetic,nulluntil the co-author confirms), versions, reviews, the comment tree (roots and replies in creation order), the current flag round against the threshold, disputes and the full content record.GET /articles/:id/comments?root=is one thread ordered by(depth, created_at).GET /users/:addressderives publications withclaimableandclaim_allowed(accepted, open status, claimable > 0), reputation by field, reviews, comments and a timeline (union of the user’s own events, newest first, 200 max).GET /users/:address/notificationslists newest first with anunreadcount; marking as read needs an authenticated writer and is left to M4 (a signed request or a local-only UI state).GET /fieldscounts non-retracted articles per field.GET /statusreports genesis, watermark, schema version and row counts. - Content: a directory whose canonical re-hash does not reproduce the CID is recorded as
unavailablewith a notice (the bytes obtained are not the content the chain names);oversizedis decided before re-hashing (20 MiB, §14.1);malformed=index.mdmissing or without readable front-matter;mismatch= field, secondary field, type, parent orauthors[0]differ from the chain. Records are re-checked everyARIADNE_CONTENT_RECHECK_SECONDS(default 6 h) and keep the last title/abstract while unavailable. - Runtime:
node:sqlitebinds JavaScript numbers as REAL, so integer arithmetic in SQL casts bound parameters (CAST(? AS INTEGER)in the ranking); rows come back with a null prototype and are copied to plain objects before leaving a query. - Acceptance on LocalNet (2026-09-30): after the contract suite populated the chain (74 LocalNet tests, 4 min),
test/localnet/catchup.test.tscaught up from round 0 and compared every Article and CoAuthor box with the projections, then rebuilt fromchain_eventswith an identical fingerprint. Numbers: 738 application calls, 912 events, rounds 1-1131 in 3.2 s; 79 Article boxes and 211 CoAuthor boxes equal field by field; second catch-up applied 0 events. The CLI ran the same chain end to end (ingestin three polls of 500 rounds,rebuildof 912 events in 137 ms,contentwith a local directory source,status); the fixture CID published by the suite validates asmismatchthere because the suite’s author is a LocalNet account while the fixture’sauthors[0]is the zero address, which is exactly the check of §14.3. Offline: 41 tests (npm test),tsc --noEmitand prettier clean. Hosting is deferred to M4 (D-070). - Spec refs: §14.3, §15 · Tests: projects/indexer/test/api.test.ts, content.test.ts, rebuild.test.ts, localnet/catchup.test.ts
D-079 M4 order and web stack (owner: “dale” to option 1 = web against LocalNet first)
Section titled “D-079 M4 order and web stack (owner: “dale” to option 1 = web against LocalNet first)”- Date: 2026-09-30 · Status: accepted
- Order inside M4: (1) web app developed and accepted against LocalNet with the M3 indexer; (2) indexer + gateway hosting on a VPS; (3) TestNet deployment with a fresh
algokey pq generategovernance key held by the owner; (4) publishing guide completed. Pinata (Path A on public networks) is wired in step 3, when the owner has the credentials; until then Path A works against the dev pin target below and Path B works everywhere. - Stack (package.json, exact, verified on npm 2026-09-30): next 16.3.8 (Node runtime, app router, no static export: dynamic routes), react / react-dom 19.3.0, typescript 5.9.3, algosdk 3.8.0, @algorandfoundation/algokit-utils 9.2.2, @txnlab/use-wallet-react 5.0.1, react-markdown 10.1.0, remark-gfm 4.0.1, remark-math 6.0.0, rehype-sanitize 6.0.0, rehype-katex 7.0.1, katex 0.16.47 (rehype-katex 7 requires ^0.16; katex 0.18 is not usable), multiformats 14.0.5, ipfs-unixfs-importer 17.1.1, ipfs-unixfs-exporter 16.2.3, blockstore-core 7.0.1, @ipld/car 5.4.7; dev: @types/node 26.6.3, @types/react(-dom) 19.3.0, vitest 5.0.3, prettier 3.9.9, @algorandfoundation/algokit-client-generator 6.0.1 (typed TypeScript client generated from the committed ARC-56 into
src/contracts/ariadne.ts, regenerated bynpm run generate:client). Tailwind is dropped: the design of §16 (monochrome, one accent, no cards) is a few hundred lines of plain CSS and one dependency less. - Spec refs: §16, §19 · Tests: projects/web/test
D-080 Network in the URL, wallet checks and the dev network
Section titled “D-080 Network in the URL, wallet checks and the dev network”- Date: 2026-09-30 · Status: accepted
- Routing (§16.1):
app/[network]/...serves every page;proxy.ts(Next 16’s middleware) rewrites a path whose first segment is not a known network to/mainnet/..., so/a/123is MainNet and/testnet/a/123is TestNet; bare/redirects client-side to the network remembered inlocalStorage, else to the first enabled network.devOnlyentries (localnet) are served only whenNODE_ENV !== 'production'orARIADNE_DEV_NETWORKS=1. A disabled network (MainNet before launch) is a 404 page that says so and links to the enabled ones. The manifest entry of the selected network is loaded on the server fromspec/networks.jsonand handed to the client through a React context; nothing from another network is fetched, cached or shown. Switching networks navigates to the same path under the other prefix; a page that does not exist there falls back to that network’s feed. - Wallet: use-wallet is initialised for the selected network only (
defaultNetwork, no in-app network switching of the wallet manager). Sign buttons are enabled only when use-wallet’s active network equals the route’s network and algod’s genesis hash equals the manifest’s (checked once per page load through/statusof the indexer, which already refused to start on a mismatch, and through algodversionsin the client); every transaction is built with the manifest’s genesis hash, so a wrong signature is rejected by the network anyway. Wallets: LocalNet = KMD (unencrypted-default-wallet, token from the manifest) and mnemonic (dev only); TestNet/MainNet = Pera, Defly, Lute. Falcon-1024 (pqsig) wallets are not supported by any wallet today: the fee preview assumes an Ed25519 sender and says so. - Spec refs: §16.1 · Tests: projects/web/test/networks.test.ts, manual on LocalNet
D-081 Transactions from the browser: deposits by box existence, resources by simulate, exact fees
Section titled “D-081 Transactions from the browser: deposits by box existence, resources by simulate, exact fees”- Date: 2026-09-30 · Status: accepted
- Deposits (§13, D-001) are computed by a port of
tests/ref/mbr.pyandtests/ref/keys.py(src/lib/mbr.ts,src/lib/keys.ts, tested against the M2_REPORT table): publish = 212 200 + 29 300 per declared co-author; vote_article 28 900; vote_review / vote_comment 32 100; flag 26 500; claim_reputation 25 300 per missing reputation box (primary, and secondary when the article has one); submit_review 43 300 + 22 500 + 25 300 if the reviewer’s box in the primary field is missing; comment 56 500 + 25 300 likewise. Box existence is read from algod (getApplicationBoxByName, 404 = missing) right before building the group, never from the indexer, so the payment is exact even when the indexer lags. Overpayment is never sent. - Box and asset references are populated by algokit-utils’ simulate (
populateAppCallResources), which readsarticle_seq_nextat that moment; on public networks a publish that fails because another publication landed in between is retried once with fresh resources (D-032).extend()calls are added as D-073 says:ceil(boxes / 8) - 1, boxes = 3 + secondary + parent + co-authors. - Fees: the preview shown before signing is
min_fee x (outer transactions + inner transactions)with inner = 1 for publish, 4 for claim_article, 1 for new_version and 1 for a retraction, 0 otherwise (D-013, D-027 for Ed25519 senders); the group is sent withcoverAppCallInnerTransactionFeesand amaxFeeequal to that preview, so the wallet never signs more than what was shown. Every transaction carries a distinct noteariadne/<method>/<ms>(D-051). - Every screen shows fee + deposit and the target network before the sign button is enabled (§16 “MUST show the total cost”).
- Spec refs: §12, §13, §16, §21.C · Tests: projects/web/test/mbr.test.ts, fees.test.ts, localnet/flow.test.ts (preview equals what the chain charged)
D-082 Content in the web app: canonical CID in the browser, pin providers, dev pin target and dev gateway
Section titled “D-082 Content in the web app: canonical CID in the browser, pin providers, dev pin target and dev gateway”- Date: 2026-09-30 · Status: accepted
- The browser builds the UnixFS DAG with the same importer and options as
spec/fixtures/compute-cid.mjsand the indexer (src/lib/unixfs.ts, tested againstexpected-cid.json), shows the CID, writes a CAR and hands it to aPinProvider; the provider’s CID must equal the local one or the flow stops (§14.4). Providers:DevPinProviderposts the CAR to the app’s own/api/dev/pin, which unpacks it intoARIADNE_CONTENT_DIR/<cid>/(the same directory the indexer validates from and the dev gateway serves), only fordevOnlynetworks;PinataProvideris added in the TestNet step (D-079) with the JWT kept inlocalStoragebehind a “forget” action, never sent anywhere but Pinata (§14.4 “provider credentials”). - Path B fetches the CID through the network’s gateway as a CAR (
?format=car), verifies every block, re-hashes with the canonical parameters, requiresindex.md, validates the front-matter against the form and the size againstMAX_CONTENT_BYTES, then enables signing. - Reviews and comments are one Markdown file with a raw-codec CID (§6.1): the bytes must fit one chunk (≤ 256 KiB, enforced in the composer) so that
CID = sha2-256(bytes)with codec 0x55; they are pinned through the same provider (pinRaw) and read back through the gateway as/ipfs/<cid>. - Dev gateway: for
devOnlynetworks withipfsGatewayempty, the app servesARIADNE_CONTENT_DIRat/api/dev/content/<cid>[/path](plain files, and?format=carfor trustless fetches). This is the only case where assets share the app’s origin; on public networks assets come from the manifest’s gateway origin, as §16 requires. Article Markdown is read on the server (from the directory in dev, from the gateway otherwise) and rendered with react-markdown + remark-gfm + remark-math, sanitised with rehype-sanitize (default schema plus themath-inline/math-displayclasses) before rehype-katex; relative URLs are rewritten to the gateway,javascript:anddata:URLs are dropped. - Spec refs: §6.1, §14.1, §14.3, §14.4, §14.5, §16 · Tests: projects/web/test/content.test.ts, lib.test.ts, localnet/flow.test.ts
D-083 Web app accepted against LocalNet (M4 step 1 of D-079)
Section titled “D-083 Web app accepted against LocalNet (M4 step 1 of D-079)”- Date: 2026-09-30 · Status: accepted
- Built and verified:
next buildclean (routes/,/[network],/[network]/a/[id],/[network]/publish,/[network]/u/[address],/[network]/inbox,/[network]/fields, the two dev API routes and the proxy),tsc --noEmitand prettier clean; vitest 18 tests: 14 offline (key lengths, every deposit of the M2_REPORT table, extend() counts, fee shapes, CID shape, status tables, network routing, canonical CID of the fixture from the browser importer, CAR round trip with a corrupted block rejected, front-matter validation) and 4 on LocalNet throughsrc/lib/ariadne.tswith generated Ed25519 signers: publish with 7 declared co-authors (11 boxes, oneextend(), 3 transactions), preview deposit 417 300 + fee 4 000 equal to the balance delta; co-author confirmation; a stranger’s vote; a declared co-author who votes is then refused confirmation; claim_reputation priced at 50 600 (both boxes) and 0 the second time; review (91 100), useful vote, comment with two mentions (81 800), reply, upvote, resolve, flag refused below MIN_FLAG_REP, new version, status change, claim of the frozen ASA with opt-in in the group, retraction, every fee equal to its preview. - In the browser (Next dev server, the M3 indexer serving with the dev content directory, KMD wallet of LocalNet): connected, published article #3 from the template (CID computed in the browser, pinned to the dev directory, cost shown 0.2152 ALGO, fees charged 0.003 ALGO as previewed), the article page rendered the Markdown with KaTeX and the figure through the dev gateway, the indexer validated the content as
valid, then set the status to under review, posted a comment with math (raw CID pinned, 0.0838 ALGO with the first reputation box), and claimed the article token (opt-in + claim_article, 0.106 ALGO shown); profile and inbox pages rendered for the wallet. - Fixes found only by running it: use-wallet’s
transactionSigneris a new function on every render, souseChainkeeps it behind a stable wrapper (otherwise the chain connection flapped and the sign button was intermittently disabled);NetworkConfigBuilder.addNetworkrefuses the reserved idlocalnet(uselocalnet()); the shared transaction status of the author panel is reset when another panel opens; the generated client is compiled with// @ts-nocheckbecause it does not meetexactOptionalPropertyTypes(scripts/patch-client.mjs). - Not exercised in the browser (covered at the library level by the flow test): voting, reviewing and flagging from a second wallet, network switching with more than one enabled network, Pera/Defly/Lute (no public network yet). Remaining for M4: indexer + gateway hosting, Pinata provider, TestNet deployment and governance key, publishing guide.
- Spec refs: §16 · Tests: projects/web/test, manual run recorded here
M4 steps 2-4 (hosting, TestNet) — recorded 2026-10-01, before the code
Section titled “M4 steps 2-4 (hosting, TestNet) — recorded 2026-10-01, before the code”D-084 Indexer on public networks: start round, indexer catch-up, content through Kubo, second copy
Section titled “D-084 Indexer on public networks: start round, indexer catch-up, content through Kubo, second copy”- Date: 2026-10-01 · Status: accepted
- Context: a fresh database starts at watermark 0; on TestNet that is 67 million rounds of scanning. Section 14.4 asks the indexer to pin every CID it validates as a second copy. The public gateway must not become an open proxy for arbitrary IPFS content.
- Decision: the manifest entry gains
startRound(the round the application was created in, written by the deploy script) andalgoIndexer(an Algorand indexer URL used only for catch-up, AlgoNode for TestNet/MainNet). A database without a watermark starts atstartRound - 1. WithalgoIndexer, polls usecatchup-with-indexer(at most 100 000 indexer rounds per poll), otherwisesync-oldeston algod (AlgoNode’s algod is archival). - Content source order:
ARIADNE_CONTENT_DIR(dev) >ARIADNE_KUBO_API(Kubo RPCdag/export, the CAR is block-verified and re-hashed exactly like a gateway CAR) > the manifest’sipfsGateway(trustless CAR). WithARIADNE_KUBO_APIthe indexer pins through Kubo RPCpin/addevery article CID it validated asvalidand every review and comment CID (raw blocks, self-verifying); pin state lives in a new base tablepins(cid, kind, article_id, pinned_at, attempts, last_error)that survives rebuilds. This is schema version 2, the first real migration (v1 databases gain the table in place). - The public gateway is the same Kubo with
Gateway.NoFetch = true: it serves only blocks the node already holds (fetched by the indexer, pinned), never arbitrary content, and answers on its own origin with a sandboxing Content-Security-Policy. - Spec refs: §3.2, §14.4, §14.6, §15 · Tests: projects/indexer/test (start round, sync choice, Kubo source and pinner against a mock RPC, v1 -> v2 migration)
D-085 Pinata as the Path A provider on public networks
Section titled “D-085 Pinata as the Path A provider on public networks”- Date: 2026-10-01 · Status: accepted
- Decision: the browser uploads the CAR it built (
POST https://uploads.pinata.cloud/v3/files, multipartfile,network=public,car=true,name) with the author’s own Pinata JWT and comparesdata.cidwith the CID computed locally; any difference stops the flow (§14.4). Reviews and comments are uploaded the same way as a one-block CAR whose root is the raw CID, so the provider cannot re-encode them. The JWT is the author’s secret: kept only inlocalStorageof their browser under a per-origin key, shown masked, with a “forget” button, sent nowhere but Pinata; the UI tells the author to create a key restricted to uploads. Pinata’s CAR import needs a paid plan (D-026); without a JWT the flow offers Path B. - Path B on public networks fetches through a public trustless gateway (
fetchGatewayin the manifest, defaulthttps://trustless-gateway.link) because our own gateway does not fetch (D-084); every block is verified, so the gateway is not trusted. - Spec refs: §14.4 · Tests: projects/web/test/pinata.test.ts (mock upload endpoint: CID equality, mismatch refusal, errors)
D-086 Hosting: one VPS, Docker Compose, Caddy
Section titled “D-086 Hosting: one VPS, Docker Compose, Caddy”- Date: 2026-10-01 · Status: accepted (provisioning is the owner’s action)
- Decision:
deploy/holds a Compose stack for one Linux VPS (Ubuntu 24.04, 2 vCPU, 4 GB, 40 GB is enough for TestNet):indexer-testnet(laterindexer-mainnet, one process per network, D-076),web(one Next server for every enabled network),kubo(IPFS node: RPC internal only, gateway with NoFetch),caddy(automatic HTTPS for three hostnames: the app,api.andipfs.). Images are pinned by digest: node 26.5.1-bookworm-slimsha256:9e6f9357d371591e32ab6f2d8a26d63bdd0d17c29eee3f4f3e7e454d9634bf73, ipfs/kubo v0.43.1sha256:b293923d66e490e70ced64df42ea7a6cf7eac2740e3fb29101df18070fa7be48, caddy 2.11.2-alpinesha256:834468128c7696cec0ceea6172f7d692daf645ae51983ca76e39da54a97c570d. The repository checkout is the source of the manifest (mounted read-only), so enabling a network or filling an app id isgit pull+ restart. Server-side calls use internal addresses (ARIADNE_INTERNAL_INDEXER_<NET>,ARIADNE_INTERNAL_GATEWAY,ARIADNE_INTERNAL_ALGOD_<NET>), the browser uses the manifest’s public URLs. Only ports 80/443 (Caddy) and 4001 (IPFS swarm) are exposed. - The stack is validated on this machine against LocalNet with
deploy/compose.local.yml(hostnames*.localhost, Caddy’s internal CA) before any VPS exists. - Spec refs: §3.2, §15, §16 · Tests: deploy/README.md checklist, local stack run recorded in D-088
D-087 TestNet governance key and deployer (amends D-043 for TestNet only)
Section titled “D-087 TestNet governance key and deployer (amends D-043 for TestNet only)”- Date: 2026-10-01 · Status: accepted
- Context: seeding 42 fields needs 42 governance-signed groups; signing them offline with
algokey pq signis 84 manual signatures for a network whose coins have no value. - Decision: on TestNet the governance Falcon-1024 key is generated by
projects/contracts/scripts/testnet_keys.pyfrom 32 random bytes into%USERPROFILE%\.ariadne\testnet-governance.seed(outside the repository, never printed); the same script creates the Ed25519 deployer and writes its mnemonic to the gitignoredprojects/contracts/.env.testnet. Only the two addresses are printed, for the owner to fund at the TestNet dispenser (a captcha or GitHub login: the owner’s action). The deploy script acceptsGOVERNANCE_PQ_SEED_FILEon TestNet, seeds the fields, and recordsstartRoundandalgoIndexer. MainNet keeps D-043 unchanged (key generated and held offline by the owner; decided again before MainNet). - Keys generated on 2026-10-01 (addresses only; the seed and the mnemonic never left the owner’s PC): TestNet governance (Falcon-1024)
ZAL425O4GBTJWID26DSTEWFU5JZGUJIJQ52GO6WVLMZEVXZTNQN63JBGJI, deployer5MHQYUCI2SQW4THJSOTFKWHFZCAVPU6TWOTRUHMEH6LFCDAIXZRRLFYL7A. Needed: 0.8974 ALGO and 0.3805 ALGO. A dry run ofalgokit project deploy testnetread.env.testnet, loaded the seed, checked both balances on TestNet and stopped before sending anything. - Spec refs: §12.1, §3.2 · Tests: deploy on LocalNet with the same code path; TestNet run recorded when funded
D-088 Hosting stack rehearsed on the development PC (M4 step 2 prepared; the VPS itself is the owner’s action)
Section titled “D-088 Hosting stack rehearsed on the development PC (M4 step 2 prepared; the VPS itself is the owner’s action)”- Date: 2026-10-01 · Status: accepted
- Built and run:
deploy/docker-compose.ymlwithlocal-stack.env(LocalNet on the host,*.localhostnames on port 8443, Caddy’s internal CA). Both images build from the repository (the web image prunes dev dependencies andnext startruns from it); Kubo appliesdeploy/kubo/init.sh(Gateway.NoFetch = true, RPC on the container network only). projects/web/test/stack/stack.test.ts(run withARIADNE_STACK=1) passed in 21 s: a new Ed25519 author posts a directory CAR to the stack’s dev pin target, which unpacks it and imports it into Kubo; the author publishes on LocalNet; the containerised indexer (starting at the manifest’sstartRound) ingests it, fetches the directory through Kubo RPC, validates it asvalidand pins it; the gateway servesindex.mdand the figure with the sandboxing CSP and refuses a CID the node does not hold; a comment’s raw CID is pinned too (2 pins); the app renders the article server-side through the internal indexer and gateway, with KaTeX and asset URLs on the gateway origin; unknown API paths are 404.- Fixes found by running it: an empty
ACME_EMAILmakes Caddy refuse the configuration, so the variable is now mandatory in the compose file; Node’slookupoption is called with{ all: true }(test helper); the indexer logged every idle poll and every API request: it now logs polls with calls or catch-up, and only 5xx responses (corrected by D-164: Caddy keeps no access log either). - Pins added while wiring wallets after D-079: @txnlab/use-wallet-kmd 5.0.1, -mnemonic 5.0.1, -pera 5.0.1, -defly 5.0.1, -lute 5.0.0; server-only 0.0.1. The root
.gitignore(Python template) ignoreslib/:projects/web/.gitignorere-includessrc/lib/. Automatic git maintenance is disabled in this clone (maintenance.auto false,gc.auto 0): the geometric repack after each commit hung the shell twice. - Remaining for M4, all needing the owner: a server and a domain (deploy/README.md section A), TestNet funding at the dispenser (section D;
scripts/testnet_keys.pyprepares the keys), a paid Pinata account for Path A on TestNet. - Spec refs: §14.4, §15, §16 · Tests: projects/web/test/stack, projects/indexer/test/public.test.ts, projects/web/test/pinata.test.ts
D-089 Content checks are scheduled so that unreachable content never delays a new article
Section titled “D-089 Content checks are scheduled so that unreachable content never delays a new article”- Date: 2026-10-01 · Status: accepted
- Context: the first run of the stack against a LocalNet holding 80 articles whose CIDs exist nowhere (contract test data) left a new article unvalidated: the indexer tried the backlog in article order, each fetch through Kubo waiting up to 120 s. On a public network the same happens with any author whose content is unreachable.
- Decision: each content cycle checks at most
ARIADNE_CONTENT_PER_CYCLE(20) CIDs,ARIADNE_CONTENT_CONCURRENCY(4) at a time; never-checked CIDs come first, newest article first; re-checks follow, least recently checked first; every free worker re-reads the queue, so an article published during a cycle is taken by the next free worker; a Kubo fetch gives up afterARIADNE_CONTENT_TIMEOUT_SECONDS(30, passed to Kubo astimeout=too). Unavailable content is retried at the normal re-check interval (6 h). - Pinning (the second copy) runs in its own loop, 4 pins at a time, newest article first, 60 s per pin (Kubo
timeout=too), failures retried with the D-084 backoff: the second run showed the content loop blocked behind sequential 5-minute pins of the contract suite’s unreachable review and comment CIDs. With both fixes the stack acceptance test passes in 32 s with 80 unreachable articles and their reviews and comments in the backlog. - Spec refs: §14.3, §14.6, §15 · Tests: projects/indexer/test/public.test.ts (order, cap, parallelism), projects/web/test/stack
D-090 First server: OVHcloud VPS-1, sslip.io names, no ACME e-mail
Section titled “D-090 First server: OVHcloud VPS-1, sslip.io names, no ACME e-mail”- Date: 2026-10-01 · Status: accepted
- The owner chose OVHcloud VPS-1 after a price comparison (4.61 EUR/month with VAT against about 29 USD for the Vultr plan first suggested): 2 vCores, 3.7 GiB RAM, 38 GB disk, Ubuntu 26.04 LTS, IPv4 SERVER_IP. Docker comes from Docker’s official apt repository (it supports 26.04, codename resolute).
- Access: a dedicated ed25519 key created on the owner’s PC (
~/.ssh/ariadne_vps); the owner installed its public half and was asked to change the provider-issued password, which had been pasted in chat. The assistant never uses the password. Password login stays enabled; disabling it is the owner’s call. - Until a domain exists, the three hostnames are sslip.io names that resolve to the IP (
SERVER-IP.sslip.io,api.…,ipfs.…); Let’s Encrypt issues real certificates for them. Moving to a domain is an.envchange and a restart. - No ACME e-mail: Let’s Encrypt stopped sending expiry e-mails in 2025, so Caddy runs without one and the owner’s address goes to no third party (
ACME_EMAILremoved from the compose file and the Caddyfile). IPFS_STORAGE_MAX15GB for a 40 GB disk;COMPOSE_PROFILESstays empty until the TestNet app exists (an indexer without an app id refuses to start), thentestnet.- Spec refs: §15, §16 · Tests: deploy/README.md checks on the server, recorded in D-091
D-091 TestNet live on the first server (M4 steps 2 and 3)
Section titled “D-091 TestNet live on the first server (M4 steps 2 and 3)”- Date: 2026-10-01 · Status: accepted
- Contract:
algokit project deploy testnetcreated app 772965337 (application addressOPQ75K3R6KYS4GFICQXKJASZFVCMBY2CD5V6JFFQGAP5WMQTJRUQ4VB2KE, creation round 67842232, transaction2JRKOVA6ZBNDRFUP26KTOHGIGC7A3UHVBZUOCEHJL6NPB6YO235A), funded it with exactly 100 000 microAlgo and seeded the 42 fields in six groups signed by the Falcon-1024 governanceZAL425O4GBTJWID26DSTEWFU5JZGUJIJQ52GO6WVLMZEVXZTNQN63JBGJIon the public TestNet. The manifest’s testnet entry carriesappId,genesisHash,governance,startRound,indexerApihttps://api.SERVER-IP.sslip.io/testnet,ipfsGatewayhttps://ipfs.SERVER-IP.sslip.ioandenabled: true. - Server (OVHcloud VPS-1, D-090): system updated and rebooted for libc6, Docker 29.8.2 / Compose 5.5.1 from Docker’s repository, ufw allowing 22, 80, 443 (tcp and udp) and 4001; the repository arrives as a git bundle in
/opt/ariadne;deploy/.envholds only hostnames and profile. Let’s Encrypt issued certificates for the three sslip.io names (issuer YE2, valid to 30 Dec 2026). - Checks: the TestNet indexer started at round 67842232 and caught up through AlgoNode’s indexer in 3 s (43 events, 42 fields);
https://api.SERVER-IP.sslip.io/testnet/statusanswersnetwork: testnetand the app id; the web serves/testnetwith the TestNet banner, the network selector offers only TestNet,/testnet/fieldslists 42 registered fields, the console is clean; from the PC,ARIADNE_TEST_NETWORK=testnetruns the catch-up comparison against the public chain and passes. - Fix found on the server: the banner read “testnet” because server components imported the label table from a
'use client'module, where a non-component export is only a client reference; the table now lives insrc/lib/networks.ts. - Not exercised yet on TestNet: a publication from a browser wallet (needs the owner’s Pera/Defly/Lute wallet with TestNet ALGO, and either a paid Pinata key for “Upload” or a CID pinned elsewhere for “I have a CID”).
- Spec refs: §3.2, §12.1, §15, §16 · Tests: listed above
D-092 Filebase as a second Path A provider (the owner uses Filebase, not Pinata)
Section titled “D-092 Filebase as a second Path A provider (the owner uses Filebase, not Pinata)”- Date: 2026-10-01 · Status: accepted
- Context: the owner has a Filebase account and asked how to hand over its access key and secret. Provider credentials are the author’s secrets (§14.4): they never go to chat, to the assistant or to Ariadne servers.
- Decision: the Upload tab offers Pinata or Filebase; the author picks one and pastes its key in their own browser, where it is stored (
localStorage, one provider at a time) with a “forget” action. Filebase is used through its IPFS RPC API:POST https://rpc.filebase.io/api/v0/dag/import?pin-roots=true, multipart fieldfile= the CAR built in the browser,Authorization: Bearer <token>; the root CID in the NDJSON answer (Root.Cid./) must equal the local CID andPinErrorMsgmust be empty. Reviews and comments go up as one-block CARs, as with Pinata. The token is Filebase’s bucket-specific “IPFS RPC API” token (Access Keys page), not the S3 access key and secret, which Ariadne never uses. The endpoint answers CORS preflights withAccess-Control-Allow-Origin: *and allows theAuthorizationheader (checked 2026-10-01), so the upload goes straight from the author’s browser to Filebase. - Spec refs: §14.4 · Tests: projects/web/test/pinata.test.ts (Filebase mock: request shape, root equality, pin error, 401)
D-093 What an article may contain, pinned by tests
Section titled “D-093 What an article may contain, pinned by tests”- Date: 2026-10-01 · Status: accepted
- The renderer (react-markdown 10 + remark-gfm + remark-math + rehype-sanitize + KaTeX) accepts CommonMark with the GitHub extensions (tables, task lists, strikethrough, footnotes, autolinks) and LaTeX mathematics between
$...$(inline) and$$lines (display).$$...$$on one line is inline math, so the templates and the guide now use the block form (the committed fixture keeps its one-line formula: changing it would change its CID). Raw HTML is never rendered: inline tags are dropped and their text kept, HTML blocks are dropped with their content. - Images are embedded only from the article’s own directory on the gateway; an external image is shown as a link (“external image not shown”), because it could change, disappear or track readers (the guide already required relative paths; the renderer now enforces it). Links: http(s), mailto, in-page anchors and files of the directory (rewritten to the gateway); every other scheme is dropped.
- The front-matter parsers of the web and of the indexer no longer cut a value at a
#inside quotes or brackets, and no longer split a quoted list item at its commas ("Results #2",["heart rate, recovery"]). - Spec refs: §14.2, §16 · Tests: projects/web/test/markdown.test.ts, content.test.ts; projects/indexer/test/content.test.ts
D-094 The upload template follows the form; the cost box says whether the account can pay
Section titled “D-094 The upload template follows the form; the cost box says whether the account can pay”- Date: 2026-10-01 · Status: accepted
- Context (owner, TestNet): choosing type “notes” still produced
type: articlein the Upload template, and signing with an unfunded wallet showed algod’s raw “overspend” error. - Decision: the template is built from the form (authors with the declared co-authors, field, secondary field, type, parent) with a body suited to each type; the draft lives in the publish form, so going back to the metadata keeps the text, and continuing rewrites only the checked header keys (
syncFrontMatter); a “Fill the header from the form” button does the same on demand. The cost box shows the ALGO the connected account can spend (balance minus its minimum balance) and warns when it is not enough, with the TestNet dispenser link on TestNet; an overspend or below-minimum error from any action is reported in plain words. - Spec refs: §14.2, §16 · Tests: projects/web/test/content.test.ts (templates for the five types, header sync with block lists, missing header and CRLF), lib.test.ts (low-funds recognition)
D-095 Visual refresh: application shell, neutral tokens, empty states (owner request, incremental)
Section titled “D-095 Visual refresh: application shell, neutral tokens, empty states (owner request, incremental)”- Date: 2026-10-01 · Status: accepted
- Context: the owner asked for a modern, sober, neutral interface that uses the whole viewport, with a clear hierarchy (navigation, network/wallet context, content, filters/actions, empty states), accessible control states and a proper mobile pattern, without touching routes, data or behaviour and without new dependencies.
- Decision: one stylesheet (
app/globals.css) rewritten on design tokens (neutral greys, one blue accent, 6-8 px radii, borders instead of shadows, a system font stack, light and dark), keeping every existing class name. Desktop (>= 1024 px): a full-height left sidebar (brand, main navigation with the current page marked, network and wallet at the bottom) and a content area that uses the width, while text keeps a reading measure of about 72 characters. Tablet: the sidebar becomes a sticky top bar. Phone (< 640 px): a compact top bar with network and wallet, and the five sections as a bottom tab bar (text only, one tap, no hidden menu). The feed gets a page header, a Top/New segmented control, a filter panel that sits beside the list on wide screens and above it on narrow ones, abstracts clamped to three lines, and empty states that say what to do next (publish, read the guide, or clear the filters). Inputs, selects and buttons get visible hover, focus and disabled states; a skip link leads to the content. - Kept on purpose: the coloured TestNet banner (§16.1 requires a persistent one off MainNet; it is now a slim strip) and §16’s “no cards or shadows” (lists are rows with dividers inside one bordered container; panels only for forms, filters and empty states).
- Spec refs: §16, §16.1 · Tests: unchanged behaviour (vitest, next build); visual checks at 1440, 1024, 768 and 375 px recorded in the commit
D-096 ARIADNE wordmark, colour-coded fields chosen by name, an About page (owner proposals)
Section titled “D-096 ARIADNE wordmark, colour-coded fields chosen by name, an About page (owner proposals)”- Date: 2026-10-01 · Status: accepted
- Name: the interface writes the name in capitals, ARIADNE, everywhere a reader sees it (wordmark, titles, page texts, the guide and the About page). The wordmark is the word alone, drawn as thin, widely spaced geometric capitals in an inline SVG that follows the text colour (light and dark), so it looks the same on every system without a web font; the favicon is its first letter. Code, package names and developer documents keep “Ariadne”.
- Fields: each of the six OECD areas has a colour (a muted palette that stays distinguishable for the common colour-vision deficiencies, light and dark variants), and every field appears as a small tag with a dot of its area’s colour and the field’s name; the code remains only as a tooltip and in the URL. Colour is never the only cue: the name is always written. Every field picker is a list grouped by area with the field names (the feed filter, the publish form’s primary and secondary fields); nobody types a code.
- About:
/<network>/about, rendered fromspec/about.md, in two layers: “In short” (what ARIADNE is for, its principles, what the blockchain does here in plain words, costs, TestNet) and “In detail” (what is on chain and on IPFS, the article lifecycle, co-authors, vote weight, every reputation rule with the exact arithmetic and a worked example, flags and disputes, governance, deposits, what runs off chain, known limits). Every number in it comes from the contract constants and the M2 measurements. - Found while writing it: the article page said the secondary field receives “the same amount” when claiming; the contract gives half (
granted_primary // 2, §10.2); the text is corrected. KaTeX now runs with the limits §18 asks for (trust: false,maxSize,maxExpand, no error thrown). - Layout found in the visual check: on phones the wordmark is drawn smaller so brand, network and wallet stay on one 56 px line (at 375 px they wrapped and doubled the sticky header); field tags let long names wrap inside the tag instead of cutting them (“Electrical engineering, electronic engineering, information engineering”).
- Spec refs: §1, §4, §10, §16, §18 · Tests: projects/web/test/branding.test.ts (sections, area mapping, field tags, field pickers, wordmark, app links only in our own documents, KaTeX untrusted, every number of the About page against the code constants), visual checks at 1440, 1280 and 375 px, light and dark
D-097 NFD names in place of addresses; mentions by @name.algo or @ADDRESS (owner request)
Section titled “D-097 NFD names in place of addresses; mentions by @name.algo or @ADDRESS (owner request)”- Date: 2026-10-01 · Status: accepted
- Names: wherever the interface shows an address (feed, article authors, reviews, comments, inbox, profile, the connected wallet, co-author list of the publish form), it shows the NFD name the owner has verified for it, else the short address; the full address stays in the tooltip and the profile is one click away. Only names that are syntactically valid (
[a-z0-9]{1,27}(.[a-z0-9]{1,27})?.algo), unexpired and verified for that address (caAlgocontains it) are shown. Each network names its NFD API inspec/networks.json(nfdApi: MainNethttps://api.nf.domains, TestNethttps://api.testnet.nf.domains, LocalNet none): names never cross networks. - Privacy: the reader’s browser never contacts NFD. The web server resolves names (reverse lookup in batches of 20, forward lookup for typed names) with a bounded in-memory cache (30 minutes, “no name” included; failures are not cached) and serves them to the browser through
/api/nfd; server pages resolve their own names before rendering. - Profiles:
/<network>/u/<name>.algoredirects to the profile of the name’s first verified address; a profile with a name shows it as the title with the full address under it and says the name comes from NFD and was chosen by the owner. - Mentions: the comment text is the source.
@ADDRESS(checksum valid) and@name.algoanywhere in it are mentioned (deduplicated, at most 5, never the sender); a name counts only through its first verified address, resolved when the comment is written, and that address is what the chain records. The picker (participants by address or name, a pasted address, a typed name) inserts@ADDRESSinto the text. Unresolvable names are reported before signing. Comment and review bodies render@ADDRESSas the person (name or short address, linked) and@name.algoas a link to that name’s profile; code, math and links are left alone, and articles are not touched. The indexer now returns each comment’s recorded mentions and the page shows them as “Notified”. - Spec refs: §9, §15, §16 · Tests: projects/web/test/people.test.ts, projects/indexer/test/api.test.ts (comments carry their mentions)
D-098 ARIADNE is funded by donations (owner request)
Section titled “D-098 ARIADNE is funded by donations (owner request)”- Date: 2026-10-01 · Status: accepted
- Decision: the About page says how ARIADNE is funded (no fees, no token, no advertising, no investors; deposits are locked storage, not income) and links to
/<network>/donate, which explains where the money goes and shows the donation account: one Algorand MainNet address (optionally its NFD) with a QR code (ARC-26algorand://URI), copy and “open in wallet” buttons, and a link to its public history on the explorer. Every page has a footer line “ARIADNE is free and runs on donations. Support it”. - The account is configured in
spec/donations.json(network, address, nfd, optional https links to other ways to give), so that changing it is a reviewed commit and never a server setting; invalid entries are dropped and logged, never shown. Until the owner publishes an address the page says the account is being set up. The owner creates the account in their own wallet (a dedicated one is advised) and only its address enters the repository. - Spec refs: §16 · Tests: projects/web/test/people.test.ts (file valid, validation, About text)
D-099 Account menu, theme switch, review replies and author votes in the interface (owner feedback)
Section titled “D-099 Account menu, theme switch, review replies and author votes in the interface (owner feedback)”- Date: 2026-10-01 · Status: accepted
- Account: the connected wallet is a button with the account’s name or short address that opens a menu: My profile, Inbox, Copy address, Disconnect. It renders after hydration only, so the server’s “Connect wallet” never mismatches the account the browser remembers.
- Theme: System, Light or Dark, in the sidebar on desktop and in the footer below 1024 px; the choice lives in this browser (
localStorage, keyariadne.theme) and is applied before the first paint by abeforeInteractivescript; the dark palette applies when the system prefers dark unless Light was chosen, and always when Dark was chosen. - Reviews: the contract has no replies to reviews; “Reply” on a review opens the new-comment box prefilled with “Re: review #N by @reviewer”, so the reviewer is notified. The interface hid “Useful” on reviews and “Upvote” on comments from the article’s authors, which D-057 allows; both are now offered (an author’s useful vote counts in the review’s total and grants nothing, and the cost panel says so).
- Spec refs: §8.2, §16 · Tests: projects/web/test/people.test.ts (theme), visual checks on LocalNet with a KMD wallet and on TestNet data
D-100 Domain: ariadne.press
Section titled “D-100 Domain: ariadne.press”- Date: 2026-10-01 · Status: accepted (owner registered the domain and its DNS)
- Decision: the first server answers at
https://ariadne.press(app),https://api.ariadne.press(indexer APIs) andhttps://ipfs.ariadne.press(gateway); three A records to SERVER_IP, no AAAA, no CAA; certificates from Let’s Encrypt through Caddy as before (D-090). “Press” reads as a scholarly publisher; withariadne.algo(NFD, for donations, D-098) the names match. - Transition: the former app name
SERVER-IP.sslip.ioredirects permanently to the domain with its path (LEGACY_APP_HOST), so links already shared keep working; the former API and gateway names are still served beside the new ones (a host variable may list several names).spec/networks.json(TestNet) points to the new API and gateway. What a browser stores is per site, so on the new domain a reader connects the wallet again and an author pastes the pinning key again. - Known:
www.ariadne.presshas no DNS record; with one, it is added toLEGACY_APP_HOSTand redirects too. The EU trademark check for “ARIADNE” (an EU archaeology research infrastructure uses the name) remains the owner’s open item. - Spec refs: §3.2, §16 · Tests: Caddyfile adapted and validated with the pinned image (new domain, former names, unset legacy host); live checks after deployment
D-101 Actions shown by what the account can do; vote colour; folding threads (owner request)
Section titled “D-101 Actions shown by what the account can do; vote colour; folding threads (owner request)”- Date: 2026-10-01 · Status: accepted
- Offered actions: the interface shows an action only when the contract would accept it from the connected account, and an action already taken (and not repeatable) stays as an inactive button that says so (“Voted”, “Upvoted”, “Useful”, “Flagged”, “Resolved”, “Claimed”). Without a wallet only counts are shown. Rules, as pure functions in
src/lib/rules.tswhere they encode the contract: vote on the article (not an accepted author, open status); flag (not an author, status allows it, at least MIN_FLAG_REP in the primary field;flagOffered); useful on a review (not its reviewer; authors may, D-057); upvote a comment (not its author); resolve (an accepted author, on an open comment by a non-author, §8.3;resolveOffered, which also fixes the interface offering it on authors’ comments); claim reputation only when something is claimable; review form hidden once reviewed. - State: a new indexer route
GET /articles/<id>/viewer/<address>returns what that address did on the article (article vote, review and comment votes, review written, flag in the current round, reputation in the primary field). The page asks it once the wallet is known; an action that just succeeded is kept locally until the indexer has it, and the page refreshes its counts 4 and 12 seconds later. The preview’s on-chain check still catches a vote the indexer has not seen yet. - Look: votes, “useful” and upvotes in a green vote colour (
--vote, light and dark) with their totals inside the button; one click shows the cost under the row of actions, a second confirms; flags in the danger colour; Reply, fold and secondary actions as light buttons; review recommendations coloured (accept green, minor revision blue, major revision amber, reject red); resolved comments and authors tagged. - Threads: every comment with replies folds and unfolds (“Hide replies” / “Show N replies”), and “Collapse all” / “Expand all” act on the whole list; threads deeper than three levels keep “continue thread”.
- Hydration: wallet-dependent rendering waits for hydration (
useHydrated), so the server’s “Connect a wallet” never mismatches the account a browser remembers (article actions, inbox, wallet button). - Also:
www.ariadne.pressnow has its DNS record and redirects toariadne.press(LEGACY_APP_HOST lists it), completing D-100. - Spec refs: §8, §16 · Tests: projects/web/test/actions.test.ts, projects/indexer/test/api.test.ts (viewer route); visual checks on LocalNet with a KMD wallet (voter, author and read-only views; a real upvote; folding; dark mode; phone)
D-102 Donation account published: ariadne.algo
Section titled “D-102 Donation account published: ariadne.algo”- Date: 2026-10-01 · Status: accepted (owner)
- Decision: donations go to
BJLW77K3PXDYX7IJTZYPPNLXOMNI7CVKKGTQ5MVEBR2JZIJXQBQRWI6UC4, the owner’s account behindariadne.algo(checked on 2026-10-01: valid checksum; NFD lists it as the verified address and the deposit account, and the reverse lookup of the address returns ariadne.algo).spec/donations.jsoncarries both and a second way to give: NFD segments under ariadne.algo (yourname.ariadne.algo), of which $5 per segment goes to ariadne.algo. Links in the file may carry a one-sentencenote(at most 200 characters). - The account holds no USDC opt-in today, so the page asks for ALGO; with an opt-in to USDC (ASA 31566704) the page can say “ALGO or USDC”.
- Spec refs: §16 · Tests: projects/web/test/people.test.ts (the published file, the note limit)
D-103 Presentation: statuses, relative times, notices, profile, citations, contents, avatars (owner request)
Section titled “D-103 Presentation: statuses, relative times, notices, profile, citations, contents, avatars (owner request)”- Date: 2026-10-01 · Status: accepted
- Statuses in colour, the word always written: preprint neutral, under review blue, final green, disputed amber, retracted red (feed, article, profile).
- Relative times (“3 hours ago”, “yesterday”, the date past a year) with the exact UTC time in the tooltip; the server and the first browser render write the date (no hydration mismatch), the relative form follows.
- Floating notices: a confirmed transaction is announced in a corner (“Vote confirmed”, round, fees, explorer link), stacked, closed after 7 s, read by screen readers; errors stay next to the button that caused them.
- Unread badge on Inbox in the menu: unread notifications (read state shared with the inbox page through
src/lib/inbox.ts; opening a notification marks it read) plus new activity of followed people (D-104); refreshed every minute while visible and on focus. - Comments ordered Oldest, Newest or Top (votes, ties oldest first) for top-level comments; replies keep the conversation’s order; the choice is kept in the browser.
- Profile: picture, name or address, Follow and Copy address; a summary (total reputation, fields, publications, reviews, comments); reputation per field as bars in the colour of the field’s area, with vote weight and today’s grant.
- Citations: “Cite” asks for the format (APA 7, MLA 9, Chicago author-date, Harvard, Vancouver, IEEE, BibTeX, RIS), starts on the reader’s default and offers “Use this format by default” (browser storage). Authors are written as ARIADNE knows them, NFD name or address (no real names on chain); the reference names the version read, its date, the publisher (“ARIADNE”, “ARIADNE TestNet”) and, in BibTeX and RIS, the CID. “Copy link” beside it. Formats are a pure function (
src/lib/cite.ts). - Contents and reading time: from three second- or third-level headings a “Contents” list built from the rendered article (anchors
sec-<slug>), beside the text from 1280 px (sticky, the section being read marked) and folded above it on smaller screens; “N min read” at 220 words a minute without code, formulas and link targets. - Avatars: the NFD avatar when the owner has one, fetched by this server only from NFD’s image service (
https://images.nf.domains/, no redirects, raster only, at most 256 KiB, cached), otherwise a symmetric 5 x 5 pattern generated from the public key; served by/api/avatar/<network>/<address>withContent-Security-Policy: default-src 'none'; sandboxandnosniff, so the reader’s browser never contacts a third party. The NFD reverse lookup now usesview=thumbnailto learn the avatar with the name. - Spec refs: §16 · Tests: projects/web/test/presentation.test.ts; visual checks on LocalNet (KMD wallet: follow, notice after a real upvote, badge, profile) and on TestNet data (contents, citation dialog and default, phone)
D-104 Follow people (owner request; per browser for now)
Section titled “D-104 Follow people (owner request; per browser for now)”- Date: 2026-10-01 · Status: superseded by D-109 (on-chain follows, contract v3); the browser list is offered for publication once
- Decision: “Follow” on a profile adds the address to a list kept in the browser for the connected account and network (at most 200). The inbox gets a “Following” tab with the public activity of the people followed (publications as submitter, confirmed co-authorships, reviews, comments), newest first, new items since the last visit marked, and the list itself with Unfollow. The indexer serves that activity:
GET /activity?address=...(at most 50 addresses per request,limit,before). Nothing is written on chain and the server keeps no follow lists. - Known limit, raised by the owner: a list per browser does not follow the account across devices. The owner asked for it to be fixed even if it needs the contract; options and a recommendation (a
followmethod emitting an event, no box) are inROADMAP.md, to decide before the MainNet contract. - Spec refs: §15, §16 · Tests: projects/indexer/test/api.test.ts (activity route), projects/web/test/presentation.test.ts (follow list rules)
D-105 USDC donations
Section titled “D-105 USDC donations”- Date: 2026-10-01 · Status: accepted (owner opted the donation account in to USDC)
- Decision:
spec/donations.jsonlists the accepted currencies (ALGO,USDC); checked on 2026-10-01 that the account holds the opt-in to ASA 31566704 (Circle’s USDC, creator 2UEQTE5Q…). The donation page asks for “ALGO or USDC” and offers one wallet link per currency (algorand://<address>?asset=31566704for USDC, ARC-26). - Spec refs: §16 · Tests: projects/web/test/people.test.ts
Contract v3: ORCID, DOIs, follows — recorded 2026-10-01, before the code
Section titled “Contract v3: ORCID, DOIs, follows — recorded 2026-10-01, before the code”Owner decisions (2026-10-01): implement ORCID and Zenodo; Zenodo drafts are created by ARIADNE in the author’s account and
published by the author; “this wallet is this ORCID iD” and “this article has this DOI” are events of the MainNet
contract; follows move on chain with the same mechanism. Plan: ROADMAP.md (done items) and the sections below.
D-106 Contract v3: three event-only methods
Section titled “D-106 Contract v3: three event-only methods”- Date: 2026-10-01 · Status: accepted (owner decision; reopens §2 “ORCID link is v2”)
- Decision:
declare_identity(scheme: uint8, value: string),declare_doi(article_id: uint64, version: uint16, doi: string)andfollow(target: address, on: bool)only emitIdentityDeclared,DoiDeclaredandFollowed(schema_version first, ts last, one log per call). No box, no deposit, no payment: the network fee only (0.001 ALGO; 0.003 from a Falcon wallet). Scheme 1 = ORCID; the generic method leaves room for ROR, ISNI and others without a new contract. An empty value withdraws (identity, DOI);on=falseunfollows. - On-chain rules: scheme >= 1, value <= 64 bytes; a DOI is declared by an accepted author of an existing article, for a version <= the current one (0 = Zenodo’s concept DOI, every version), non-empty DOIs are <= 200 bytes, start with
10.and need a non-retracted article; a DOI can be withdrawn in any state; nobody follows the zero address or themselves. The ORCID check character is not checked on chain (about 300 opcodes, proves nothing about ownership): the web app and the indexer check it. - Consequences: program 6 266 bytes (4 pages, 3 of them extra), deployer funding counts 100 000 µAlgo per extra page (the earlier
required_fundingmissed it). The indexer’s reducer mirrors these assertions exactly (a stricter reducer would stall ingestion). - Spec refs: §2, §11, §12 · Tests: projects/contracts/tests/unit/test_identity.py, tests/ref/test_ref_identity.py, tests/localnet/test_68_identity.py, projects/indexer/test/projections.test.ts
D-107 ORCID verified by cross-proof; never a source of reputation
Section titled “D-107 ORCID verified by cross-proof; never a source of reputation”- Date: 2026-10-01 · Status: accepted
- Decision: the declaration is signed by the address (the event); the ORCID record lists
https://ariadne.press[/testnet]/u/<ADDRESS>under “Websites & social links” with visibility “Everyone”. The indexer reads{orcidApi}/<iD>/person(names and websites in one call; a/read-publictoken when credentials are set, anonymous otherwise) and caches the result inorcid_checks:verified,unlinked,missing,invalid(check character),pending. Rechecked weekly; a failed check keeps the last result and backs off (1, 2, 4 … minutes, at most a day). The record’s names are shown only while verified. Writing to the ORCID record needs the paid member API, so the researcher adds the link by hand. “Sign in with ORCID” is a later option. - No reputation is derived from ORCID: reputation is earned in ARIADNE (§1, §9).
- These checks are cached results of an outside service, like
content(§1.6, §15): the declarations are rebuilt from the chain, the checks are not. - Spec refs: §1.6, §2, §15, §16 · Tests: projects/indexer/test/verify.test.ts, spec/fixtures/orcid.json
D-108 DOIs through Zenodo, verified against the record
Section titled “D-108 DOIs through Zenodo, verified against the record”- Date: 2026-10-01 · Status: accepted
- Decision: ARIADNE creates a draft in the author’s Zenodo account (OAuth
deposit:write, sandbox on TestNet) with the article’s files from our own gateway and its metadata (creators with verified ORCID iDs, licence,related_identifiersIsIdenticalTo the article page and the version’s CID, communityariadne-algo, DOI reserved); the author reviews and publishes it on Zenodo, then declares the DOI on chain. Published Zenodo records cannot be deleted. A DOI isverifiedwhen its record is published and IsIdenticalTo the article page and, for a version, that version’s CID (URLs can be reused across apps; CIDs cannot). Zenodo DOIs are checked with that network’s Zenodo records API; others with DataCite (MainNet) or shown asdeclared; a DataCite test DOI (10.5072) outside the sandbox isinvalid. Later versions are Zenodo “new versions”. Crossref with ARIADNE’s own prefix stays future work. - Spec refs: §14, §15, §16 · Tests: projects/indexer/test/verify.test.ts
D-109 Follows on chain (replaces D-104)
Section titled “D-109 Follows on chain (replaces D-104)”- Date: 2026-10-01 · Status: accepted (owner decision)
- Decision:
follow(target, on)events; the indexer buildsfollows(active, since) and serves/users/:a/following,/followersand/feed. Only the first follow of a pair notifies the person followed (followed), so following and unfollowing never spams. The browser list of D-104 is offered for publication once (only the difference with the chain), then cleared; “seen” marks of the feed stay in the browser. Follows are public and permanent on chain; unfollowing only marks them inactive. - Spec refs: §11, §12, §15, §16 · Tests: projects/indexer/test/projections.test.ts, projects/indexer/test/api.test.ts
D-110 TestNet restarts with the v3 application
Section titled “D-110 TestNet restarts with the v3 application”- Date: 2026-10-01 · Status: accepted
- Decision: an application is immutable (D-063), so v3 is a new TestNet application and the candidate for MainNet. The old application
772965337stays on chain but is no longer indexed: its database is kept, its content stays pinned on our Kubo node, the About page says so, and authors may publish the same CIDs again. The indexer’s database path takes{appId}(/data/testnet-{appId}.sqlite), so a new application starts with a clean database. - Spec refs: §3.2, §19 · Tests: projects/indexer/test/verify.test.ts (configuration)
D-111 “Profile” in the main menu; the inbox opens from it (owner request)
Section titled “D-111 “Profile” in the main menu; the inbox opens from it (owner request)”- Date: 2026-10-01 · Status: accepted
- Decision: the main menu (sidebar, top bar, phone tab bar) shows Profile instead of Inbox. It opens the connected account’s profile (
/u/<address>), or/me, which asks for a wallet. The unread count (notifications plus new activity of the people followed) moves to Profile. On one’s own profile an Inbox button carries the same count, and the inbox links back to the profile; the account menu keeps both entries. One’s own profile and the inbox count as the Profile section. - Spec refs: §16 · Tests: projects/web/test/branding.test.ts (sections)
D-112 Notifications bell, top right (owner request)
Section titled “D-112 Notifications bell, top right (owner request)”- Date: 2026-10-01 · Status: accepted (refines D-111)
- Decision: a bell in the top-right corner with the unread count in red (notifications plus new activity of the people followed). It opens a panel with the latest 8 notifications (unread marked; a click marks it read and opens the comment, the article or the new follower’s profile), “Mark all read”, a line for what is new from the people followed, and “Open the inbox”. On desktops it sits in a bar above the content that also carries the test-network notice; on tablets and phones in the top bar next to the wallet, and only with a wallet connected. The count leaves the “Profile” menu entry (no double badge); the Inbox button of one’s own profile keeps it. The
followednotification of D-109 gets its text (“started following you”). - Spec refs: §16 · Tests: projects/web/test/presentation.test.ts (notification links and texts)
D-113 Health checks and an outside uptime monitor
Section titled “D-113 Health checks and an outside uptime monitor”- Date: 2026-10-01 · Status: accepted
- Decision: the indexer’s
GET /healthanswers 503 when no ingestion poll has succeeded forARIADNE_STALL_SECONDS(300 by default): algod unreachable, or a round the reducer refuses (the chain and the reducer disagree, which must never pass silently). The containers of the indexer and the web app have Docker health checks (docker compose psshowsunhealthy). Outside the server, a free uptime service watched by the owner checks three addresses every five minutes and sends an email (or a phone notification) when one fails: the app, the indexer’s/health, and the gateway with an identity CID that needs no fetching (/ipfs/bafkqaaa). - Spec refs: §15 · Tests: projects/indexer/test/api.test.ts (health)
D-114 A new version starts from the current one (owner request)
Section titled “D-114 A new version starts from the current one (owner request)”- Date: 2026-10-02 · Status: accepted
- Decision: “New version” loads the files of the current version from the gateway (verified against its CID) into the editor:
index.mdwith the keys the chain checks (authors, fields, type, parent) rewritten from the article, and every other file of the directory. The template is used only when the current version cannot be read, and the page says so. - Spec refs: §16 · Tests: visual check; projects/web/test/localnet/flow.test.ts (new_version)
D-115 Replies to a review appear under it (owner request)
Section titled “D-115 Replies to a review appear under it (owner request)”- Date: 2026-10-02 · Status: accepted (refines D-099)
- Decision: a top-level comment whose first line is “Re: review #N …” (what “Reply” on a review writes) is shown, with its whole thread, under review N, without that first line; the comments section counts the rest and says how many replies sit under the reviews. A comment naming a review that does not exist stays among the comments. Nothing changes on chain.
- Spec refs: §16 · Tests: projects/web/test/feedback.test.ts
D-116 Articles are named by their title in lists (owner request)
Section titled “D-116 Articles are named by their title in lists (owner request)”- Date: 2026-10-02 · Status: accepted
- Decision: profiles (reviews, comments, timeline), the inbox, the notifications bell and the follow feed name an article by its title, cut at a word near 60 characters (48 in the bell) with an ellipsis; “Article N” only while its content has not been read. The indexer adds
titleto the reviews, comments and timeline rows of a profile and to notifications. - Spec refs: §15, §16 · Tests: projects/indexer/test/api.test.ts, projects/web/test/feedback.test.ts
D-117 Review of contract v3: text decoded from raw bytes, Zenodo hardening
Section titled “D-117 Review of contract v3: text decoded from raw bytes, Zenodo hardening”- Date: 2026-10-02 · Status: accepted
- Indexer (critical finding): the contract counts the raw bytes of
declare_identityanddeclare_doivalues and accepts invalid UTF-8; algosdk decoded such text with U+FFFD (3 bytes each) and the reducer measured it again, so one transaction could stall ingestion for ever. Strings are now decoded asbyte[]and turned into text without rejecting invalid bytes or dropping a leading BOM, and the reducer no longer re-measures what the contract measured (the ASCII “10.” prefix survives decoding). The Python oracle checks the raw bytes (surrogateescape). The verifier marks values that are not DOI-shapedinvalidwithout a lookup, puts the whole DOI in one encoded URL segment for DataCite, checks a Zenodo DOI at the instance of its prefix (10.5281 zenodo.org, 10.5072 the sandbox) whatever the network’s own Zenodo, and asks for a new ORCID token once when one is refused. Article DOI links are built from the trimmed DOI, encoded. Every request to ORCID, Zenodo and DataCite carries a User-Agent naming ARIADNE: zenodo.org answers 403 to the default one of Node.js (found on the server on 2026-10-02). - Web: a draft never touches an unpublished new version the author already has on Zenodo (refused with a link to it) and only drafts made by the request are deleted on failure; one draft at a time on the server and one per session every 30 s; the gateway read has a timeout and a byte cap enforced while streaming; the request body is capped; keywords must be text; Zenodo’s 4xx keep their status; sealed values carry their purpose; the token cookie is SameSite=Strict. Each draft also carries a reading copy (
README.md) for Zenodo’s file preview: title, authors, abstract and body without the front-matter, links into the canonical directory; the canonical files are unchanged. Following many addresses marks each confirmed group at once (no double fees after a refusal half-way), lists the addresses and drops invalid ones; the follow dialog keeps what it was opened for; the ORCID lookup shows results only for the iD they were found for; the bell gives focus back on Escape and closes when focus leaves it. The DOI form shows the network’s own Zenodo prefix as example and warns about a DOI of the other Zenodo. - Spec refs: §11, §15, §16 · Tests: projects/indexer/test/bytes.test.ts, verify.test.ts; projects/contracts/tests/ref/test_ref_identity.py; projects/web/test/zenodo.test.ts, feedback.test.ts, presentation.test.ts
D-118 Status page (owner request)
Section titled “D-118 Status page (owner request)”- Date: 2026-10-02 · Status: accepted
- Decision:
/<network>/status, linked from the footer only (after “Publishing guide”), shows what each part reports now, refreshed every 15 s while the page is visible: the application (live, articles published, governance against the manifest) and the deposits it holds; the Algorand node (round, age of the last block, genesis against the manifest) and the Algorand indexer (rounds behind); the ARIADNE indexer (ingestion, rounds behind the chain, last successful poll, counts), the second copy (pins to retry) and the ORCID/DOI checks; the website (uptime, memory) and the IPFS gateway; the governance account (warning under 1 ALGO) and the donation account on MainNet (ALGO and USDC); ORCID, Zenodo (and whether drafts are configured), DataCite and NFD. The checks run on the server in parallel with 6 s timeouts (GET /api/status?network=), one report per network every 10 s whatever the number of readers, with nothing internal in it (no internal address, no secret, failures as “not reachable”, “answered 503”, “no answer within 6 s”). The headline is “down” only when the node, the application or the indexer is down. - Spec refs: §15, §16 · Tests: projects/web/test/status.test.ts
D-119 Installable app (PWA) (owner request)
Section titled “D-119 Installable app (PWA) (owner request)”- Date: 2026-10-02 · Status: accepted
- Decision: a web app manifest (
/manifest.webmanifestfromapp/manifest.ts: name, standalone window, start URL/, which opens the network last used; icons 192, 512 and a maskable 512 generated from the mark byscripts/pwa-icons.mjs; an Apple touch icon), theme colours for light and dark, and a service worker (/sw.js, registered in production builds only). The service worker never serves protocol data from a cache: pages always come from the network, with a small offline page when there is none; only the build’s hashed static files are cached (at most 300); requests to/apiand to other origins (the indexer, algod, IPFS, wallets) are not touched. “Install app” appears in the footer when the browser offers installation, and on iPhone and iPad with the Safari steps. The PWA files live at the root with a dotted name, which the network proxy lets through. - Spec refs: §16 · Tests: projects/web/test/pwa.test.ts
D-120 Search (owner request)
Section titled “D-120 Search (owner request)”- Date: 2026-10-02 · Status: accepted
- Decision: a Search page (
/<network>/search, in the main menu after Feed; on phones Guide and About leave the tab bar for it and stay in the footer; the feed has a search field) with three tabs: articles, reviews and comments, people. The page’s state is its URL (shareable, back and forward work, no script needed); choices apply at once, typed values with Enter. Articles: full text over title, abstract, keywords, body, authors (address, verified ORCID name and iD), field names, DOIs and CID, with stemming, accents and case ignored, phrases, exclusions, OR, prefixes, limits to one part (title:…), and exact lookups by DOI, CID or#id;name.algoin the query or in a people filter becomes that address (NFD). Filters, each option with its count under the other filters: area, field (main field only), type, status (retracted hidden unless chosen), year and dates of publication and of the last version, reviewed or not, the recommendations received, vote weight, comments, DOI (shown ones only, verified), authors with a verified ORCID iD, author (submitter only), reviewer, commenter, ORCID iD, people I follow (connected wallet), language and licence (front-matter), keywords, revised, amendments and the article amended, content checked. Sorts: best match, top, newest, oldest, most voted, most reviewed, most discussed, recently updated. Reviews and comments: by their text, kind, recommendation, author, article, field, dates; a reply to a review says so. People: by verified ORCID name, iD or address, role, verified ORCID, reputation in a field. Results show the matched words highlighted in the title, abstract, body or post (as text, never HTML), authors with their ORCID names, counts and the DOI; removable chips list the filters in use; pages of 20. Suggestions while typing (titles open the article, keywords and fields become filters, people open the profile) and the last 8 searches, kept in this browser only; “/” focuses the field. - Indexer (schema v4): SQLite FTS5 tables
search_articlesandsearch_posts, kept current by theserveloop (an article is reindexed when anything shown about it changes) and rebuilt byreindex;contentgainsbody_text,language,license(every content record is read again once after the migration); the text of each review and comment is read from IPFS as one raw block (at most 100 000 bytes) and kept inpost_textsonly when it hashes to its CID. Routes/search/articles,/search/posts,/search/people,/search/suggest. The search is a cached view likecontent(§15): rebuilt from the chain plus IPFS, never an authority. A DOI whose record does not point back is neither shown nor counted as “with a DOI”. - Spec refs: §15, §16 · Tests: projects/indexer/test/search.test.ts, public.test.ts (v3 to v4 migration); projects/web/test/search.test.ts
D-121 Search: filters folded, wildcards and regular expressions (owner request)
Section titled “D-121 Search: filters folded, wildcards and regular expressions (owner request)”- Date: 2026-10-02 · Status: accepted (extends D-120)
- Decision: every group of filters starts folded; a group holding a filter in use stays open, so that the choice can be changed again. The query accepts wildcards inside words (
?one letter or digit,*any;wom?n,*ology,neur*sis; a final*stays a prefix of the index, a?that ends a word is punctuation) and regular expressions between slashes (JavaScript syntax, at most 200 characters, 5 patterns per query), limited to one part like words (title:/^sleep/) or excluded (-/x/). They are tested on the folded texts (accents removed, case ignored) of the articles the index matched, or of all of them, and their matches are marked in the results; reviews and comments and people (name, address, ORCID iD) accept them too. A broken expression matches nothing and says why. The work runs on the indexer’s only thread, where an expression can backtrack for ever: each search’s pattern work runs undervmwith a 2 s timeout (400, “make it more specific”) and all searches share 20 s of it per minute (503, “busy”); searches without patterns are not limited. RE2 (re2js, linear time) was measured and rejected: about 300 times slower on long texts. - Spec refs: §15, §16 · Tests: projects/indexer/test/search.test.ts
D-122 Lists of articles in the profile (owner request)
Section titled “D-122 Lists of articles in the profile (owner request)”- Date: 2026-10-02 · Status: accepted
- Decision: the owner chose lists that are either private or public, chosen when each list is made. Private lists live in the browser (localStorage per network): free, instant, seen by nobody else, usable without a wallet (shown under “Profile” when none is connected). Public lists are on chain without the contract, which stays as reviewed (no new method, no TestNet restart): each change is a note of a 0-ALGO payment from the owner to themself,
ariadne/lists/<appId>:followed by a JSON array of changes (name and describe a list, add or remove articles, delete it); the network fee is the whole cost (0.001 ALGO per note; one note carries up to 50 changes, the wallet signs up to 16 notes at once). The indexer subscribes to these notes next to the application calls, stores each as aListNoterow of chain_events (rebuild replays them in chain order) and projects them tolistsandlist_items(schema v5); a note that breaks the rules changes nothing and never stops ingestion; limits: 100 lists per address, 1 000 articles per list, names 80 characters, descriptions 280. Only the sender can write their lists, because only they can sign from their address. The web: “Save” in an article’s reader tools opens the lists (private ones change at once; ticks on public ones are collected and saved with one signature, with the cost shown), a new list can be created there; the profile gets tabs, Activity and Lists, where everyone sees the public lists and the owner also manages them (rename, describe, delete) and their private lists (rename, remove articles, delete, make public: one signature, then the private copy goes); each public list has its own page to share (/<network>/u/<address>/lists/<id>), where the owner can rename it, remove articles or delete it. Deleting or removing changes the list; the history stays on chain, and the dialogs say so. - Spec refs: §11, §15, §16 · Tests: projects/indexer/test/lists.test.ts, public.test.ts (v4 to v5 migration); projects/web/test/lists.test.ts
D-123 Saving from search, bibliographies of lists (owner request)
Section titled “D-123 Saving from search, bibliographies of lists (owner request)”- Date: 2026-10-02 · Status: accepted (extends D-122, D-103)
- Decision: every article in the search results has a small “Save” button that opens the same lists dialog as on the article page. Every list (the page of a public list, its card in the profile, a private list) has “Bibliography”: all its articles cited in one of the eight formats of D-103 (starting on the reader’s citation default), sorted by author then year (accents and case ignored), by year (newest or oldest first), by title, or in the order of the list (the choice is remembered in the browser); Vancouver and IEEE are numbered in the order shown (“1.” and “[1]”), BibTeX and RIS give one record after the other; it can be copied or saved as a file (.bib, .ris or .txt named after the list). Each article is cited at its current version with its citable DOI (D-108), authors as everywhere (verified ORCID name, else NFD name, else address). The indexer answers
GET /cite?ids=…(up to 1 000) or?owner=&list=(a whole public list) with the data in one request. - Spec refs: §15, §16 · Tests: projects/indexer/test/lists.test.ts; projects/web/test/bibliography.test.ts
D-124 Browser notifications and contact from ORCID (owner request)
Section titled “D-124 Browser notifications and contact from ORCID (owner request)”- Date: 2026-10-02 · Status: accepted
- Decision (the owner chose option B for both): browser notifications by Web Push, without email and without outside accounts. In the inbox (linked from the bell) the connected account turns them on for this browser, chooses the kinds (replies, mentions, reviews and comments on their articles, resolved comments, co-author invitations, new followers), sends a test or turns them off; they arrive with the browser closed, on iPhone and iPad once the app is installed on the home screen (iOS 16.4+). The web server keeps each subscription (push endpoint, its two keys, network, address, kinds) in a SQLite file on its own volume; a sender started by
instrumentation.tsasks each indexer every 30 s for the notifications created since its cursor (GET /notifications?after=, one request per network) and sends each to the browsers that asked for that address and kind, encrypted end to end with the server’s VAPID keys (web-push), linking to the comment, article or profile; nothing older than a day is sent, gone endpoints (404, 410) are dropped. Notifications are public already, so subscribing needs no signature; to keep the server from posting anywhere else, endpoints must be https URLs of the known push services (Google, Mozilla, Microsoft, Apple); at most 5 addresses per browser. Contact on profiles from ORCID: for a verified ORCID iD the verifier also keeps what the record shows to everyone (websites with their names, emails, countries, and current employments from/employments) inorcid_checks.public_json(schema v6; verified records are checked again at once after the migration), and the profile shows them under the ORCID line, marked as coming from ORCID; nothing is typed into ARIADNE and nothing goes on chain. The link back to the ARIADNE profile is not shown; only http(s) links become links. - Spec refs: §15, §16 · Tests: projects/indexer/test/verify.test.ts, api.test.ts, public.test.ts (v5 to v6); projects/web/test/push.test.ts
D-125 Names read from ORCID for citations; citation counts on profiles (owner request)
Section titled “D-125 Names read from ORCID for citations; citation counts on profiles (owner request)”- Date: 2026-10-02 · Status: accepted (refines D-103, D-108)
- Decision: many ORCID records hold the whole name in “given names” with the family name empty (ORCID shows the record as one name, “Lucia Vega Ortiz”). Citations, bibliographies and Zenodo creators therefore read such a name as given names followed by family names (
src/lib/names.ts): “Family, Given” with a comma says it outright; a particle starts the family name (de, del, van, von, da…); two words are one given and one family name; three words one given name and two family names (Spanish and Portuguese use); four or more end with two family names, joined by y/e/i when there. A filled family name in ORCID always wins, so anyone this reading gets wrong fixes it on ORCID. The Zenodo draft no longer asks for names of authors whose verified record has any name. Citations on profiles: for a verified ORCID iD the verifier also asks OpenAlex (/authors/orcid:<iD>, free, no key; same service for every network) for the citations, number of works, h-index, i10-index and citations per year of the last five years, kept inorcid_checks.metrics_json(schema v7; verified iDs are checked again at once) and rechecked with the ORCID record; a failed lookup keeps the last counts, an iD OpenAlex does not know shows none. The profile shows Citations and h-index among its numbers, with a line saying they count all the person’s works (not only those on ARIADNE), linking to OpenAlex. Citations between ARIADNE articles are not counted yet. - Spec refs: §15, §16 · Tests: projects/web/test/names.test.ts, zenodo.test.ts; projects/indexer/test/verify.test.ts, public.test.ts (v6 to v7)
D-126 Citations between ARIADNE articles, with their lists (owner request)
Section titled “D-126 Citations between ARIADNE articles, with their lists (owner request)”- Date: 2026-10-02 · Status: accepted (extends D-125)
- Decision: an article cites another ARIADNE article when its current version links to the other’s page on the same network (any form of https://ariadne.press[/testnet]/a/
), writes a DOI the other declared (verified, declared or not checked yet; never one whose record contradicts it), writes the CID of one of the other’s versions, or amends it (parent on chain). The content check keeps what each version points to ( content.refs_json: URLs, DOIs and CIDs from the text, links included, and from the front-matterreferences; every version is read again once after the migration, schema v8); the indexer derivescitations(citing, cited, how) from it and the chain in theserveloop, recomputing only when content, versions, DOIs or their checks change. An article never cites itself; retracted citing articles are not counted. The article page shows “cited by N on ARIADNE · cites M”, linking to/a/<id>/citations(tabs: cited by, references on ARIADNE; each article says how it cites); the profile shows “Cited on ARIADNE” (total citations its articles received), linking to/u/<address>/citations(each citing article, which article it cites, how, and whether it is a self-citation; the h-index on ARIADNE and the self-citations in the summary); the OpenAlex numbers become “Citations (all works)” and “h-index (all works)”. The search can sort by “Most cited on ARIADNE” and shows “cited by N” on results. Routes:/articles/:id/citations?direction=cited_by|cites,/users/:address/citations;/articles/:idand/users/:addresscarry the counts. The publishing guide says how to cite so that it counts. - Spec refs: §15, §16 · Tests: projects/indexer/test/citations.test.ts, public.test.ts (v7 to v8)
D-127 Thread, ARIADNE’s citation measure, instead of an h-index (owner request)
Section titled “D-127 Thread, ARIADNE’s citation measure, instead of an h-index (owner request)”- Date: 2026-10-02 · Status: accepted (replaces the h-index of D-125 and D-126)
- Decision: the owner wants no h-index but a measure of ARIADNE’s own, fair and comparable. Thread (after Ariadne’s thread), 0 to 100, 50 typical: (1) each article counts its external citations: from other ARIADNE articles, not retracted, sharing no accepted author with it; (2) its percentile places it among the articles of the same primary field and publication year (mid-rank: the share it is cited more than, ties counted half); when that group has fewer than 10 articles, the same area and year, then the same year, then every article; (3) a person’s Thread is the mean percentile of their articles, each weighted by their share (1 / accepted authors), with one article at 50 added (a prior: one lucky article cannot make 100, and the prior fades as articles accumulate). Amendments (corrections of one’s own work) and retracted articles take no part. Why: an h-index grows with career length, depends on the field and counts self-citations; percentiles within field and year compare unlike fields and ages, a mean (not a sum) compares short and long careers, fractional weights compare teams of any size, and the prior keeps small samples honest. It is built from established ideas (percentile normalisation, fractional counting, shrinkage) and is ARIADNE’s in its combination and in being reproducible by anyone from the chain and the articles. The indexer keeps each article’s external citations, group and percentile in
article_impact(schema v9), recomputed with the citations;/articles/:idcarriescitations.thread,/users/:addresscarriesariadne_citations.thread{score, articles}. The web shows “Thread” on profiles (linking to its explanation on the citations page), the article’s Thread percentile on its citations page, and no h-index at all (the OpenAlex h-index is no longer shown either; OpenAlex’s citation count stays as “Citations (all works)”). - Spec refs: §15, §16 · Tests: projects/indexer/test/citations.test.ts, public.test.ts (v8 to v9)
D-128 Thread compares articles at the same age, pooling every year (owner question)
Section titled “D-128 Thread compares articles at the same age, pooling every year (owner question)”- Date: 2026-10-02 · Status: accepted (refines D-127)
- Decision: the owner asked whether an old article cited for many years should not stand out even when its year had tremendous articles. Percentiles already limit the effect of a few giants (they take only the top places, unlike a mean), but comparing within the calendar year left the luck of the year, and a first idea of comparing only with older articles would have frozen the first articles of a field at 50. Thread now compares each article with every article of its field (its area, then all, when the field has fewer than 20, up from 10), each pair at the age of the younger of the two: the citations each had received by then (a citation dates from the citing article’s current version). So every year is in the comparison and a strong year weighs little; an old article keeps climbing while it is cited; a recent article is judged only on the time it has lived; the first article of a field can stand out. Percentiles are computed again every day (ages grow). Mid-rank, the share of the group it had more citations than, ties half; the person’s Thread is unchanged (mean by share, one article at 50 added). The cost is quadratic in the size of a field; fine for ARIADNE’s size, to be revisited (binning by age) when fields reach thousands of articles.
- Spec refs: §15, §16 · Tests: projects/indexer/test/citations.test.ts
D-129 The measure is called “Thread Score” (owner request)
Section titled “D-129 The measure is called “Thread Score” (owner request)”- Date: 2026-10-02 · Status: accepted (names D-127, D-128)
- Decision: everywhere readers see it (profile, citation pages, About) the measure is “Thread Score”; an article’s percentile is shown as its Thread Score. Code and API keep the field name
thread.
D-130 Scores shown in lists, votes only on the article page (owner request)
Section titled “D-130 Scores shown in lists, votes only on the article page (owner request)”- Date: 2026-10-02 · Status: accepted
- Decision: the feed, the search results and the other article lists (public lists, citations) show each article’s score as a badge on its left: the weight of its votes (each vote weighs 1 plus the square root of the voter’s reputation in the field, rounded down), with the number of votes in its description. The badge is not a control: voting stays on the article page, where its cost and rules are shown (D-101).
- Spec refs: §16 · Tests: visual check
D-131 The “Peer-reviewed” seal, computed by the indexer (owner request)
Section titled “D-131 The “Peer-reviewed” seal, computed by the indexer (owner request)”- Date: 2026-10-02 · Status: accepted
- Decision: the owner asked whether ARIADNE should only publish what met a minimum of reviews. Publishing stays free and immediate (the contract is unchanged, and an author can move an article to “final” without any review); instead a seal tells reviewed work apart. The owner approved the proposal (Claude Doc “Propuesta: sello Peer-reviewed”) with its proposed values and one change (co-authorship conflict window 3 years instead of 5). An article holds the seal while at least 2 qualified reviews are favourable (accept, minor revision) and favourable > unfavourable (major revision, reject); 2 or more unfavourable, as many as the favourable or more, show “changes requested”; any qualified review, “in review”; none, “not peer-reviewed yet”. A review qualifies when, in this order: its author is not an author of the article; the reviewer has reputation >= 10 in the primary or secondary field, or a verified ORCID iD with >= 3 works in the article’s area on OpenAlex (a permanent path, so that established researchers count from their first day; OpenAlex fields are mapped to the six OECD areas in src/fieldmap.ts, a field may belong to two); no conflict of interest with any author (the same verified ORCID iD; an ARIADNE article written together within 3 years of the review; works together on OpenAlex from 3 years before the pair’s first review to 3 years after its last, both iDs verified; the same current employer on the public ORCID records); its text was read from IPFS and has >= 200 words; and, for a favourable review, no favourable review from an author of the article to the reviewer in the 2 years before. A verified ORCID iD is not required. The seal is alive (later qualified reviews can remove it), suspended while the article is disputed and absent when it is retracted; it names the version current at the latest counted review, and the article page says when a later version is not reviewed yet. Everything is derived from the chain, IPFS, ORCID and OpenAlex (schema v10:
article_seals,review_qualifications,seal_state; base tablecoauthor_checks), recomputed when its inputs change, and each result names its rule version (SEAL_RULE1), so anyone can compute it again. OpenAlex costs: one more request per verified iD check (works grouped by field) and one per reviewer-author pair every 30 days. The web shows the seal as a badge in every list, a “Peer review” panel on the article page (each review: counts, or why not), seal counts on profiles, and a “Peer-reviewed” search filter. Status 3 (“final” on chain) is shown as “closed version”, because it says nothing about review. - Spec refs: §15, §16 · Tests: projects/indexer/test/seal.test.ts, verify.test.ts, public.test.ts (v9 to v10); projects/web/test/peerReview.test.ts
D-132 Documentation site with Astro Starlight at /docs (owner request)
Section titled “D-132 Documentation site with Astro Starlight at /docs (owner request)”- Date: 2026-10-02 · Status: accepted
- Decision: the owner asked for a GitBook-like place that explains everything in detail and in order, and chose Starlight among the options (GitBook, Starlight, pages inside the app, Docusaurus). The site lives in
projects/docs(Astro 7, Starlight 0.42), English first with translations prepared (a locale and a folder), and is built to static files served by its own small Caddy container behind the stack’s Caddy at https://ariadne.press/docs/ (no new DNS name, no outside service: Starlight’s search runs from the static files, Pagefind). Sections: Start here, Guides, Concepts, Reference, Run your own, FAQ. The protocol specification, the decision log and the fields are copied fromspec/ARIADNE_SPEC.md,DECISIONS.mdandspec/taxonomy.jsonat every build (the specification’s inlineEDIT D-nnncomments become visible notes; its section 23, the brief of the implementation assistant, is left out), so they are written once. Internal links are validated at build time; the numbers the code defines (costs, reputation, flags, the seal) are checked byprojects/web/test/docs.test.ts. The app links the site from its menu (“Docs”, desktop) and its footer (“Documentation”). The About page and the publishing guide stay in the app. - Spec refs: §16 · Tests: projects/web/test/docs.test.ts; the docs build (link validation)
D-133 A verified ORCID iD is required for a review to count toward the seal (owner, seal rule 2)
Section titled “D-133 A verified ORCID iD is required for a review to count toward the seal (owner, seal rule 2)”- Date: 2026-10-03 · Status: accepted (amends D-131)
- Decision: the owner proposed making a linked ORCID iD mandatory for reviews. Three ways were weighed: required to review (the web would hide the form), required for the seal, or not required. The contract cannot require it (the iD is checked outside the chain), and hiding the form would shut out students, people in industry and others without an iD from discussion and from the reputation that useful reviews earn. The owner chose: anyone can still review, but a review counts toward the Peer-reviewed seal only when its author has a verified ORCID iD. This closes a gap of rule 1, where a reviewer without an iD escaped the OpenAlex co-authorship and the same-employer checks. ORCID does not verify identity (anyone can open a record), so it adds traceability and the conflict checks, not protection against invented people.
SEAL_RULEis 2; the check runs right after “not an author” and its reason isorcid(“the reviewer has no verified ORCID iD”); the OpenAlex expertise path is unchanged (3 works in the area, found through the verified iD). For an author without an iD, only the ARIADNE co-authorship check applies. - Spec refs: §15, §16 · Tests: projects/indexer/test/seal.test.ts, projects/web/test/peerReview.test.ts
D-134 Storage settings on the profile, and one’s own IPFS node (owner request)
Section titled “D-134 Storage settings on the profile, and one’s own IPFS node (owner request)”- Date: 2026-10-03 · Status: accepted (extends D-085, D-092)
- Decision: the owner asked for an option on the profile to configure one’s IPFS. Until now the provider key could be set only on the publish page, so someone who wanted to review or comment first had to go there. One’s own profile now has a “Storage (IPFS)” section (shown only to its owner, like the ORCID steps) with the same setting as the publish page: Filebase, Pinata, or a third option, my own IPFS node: the address of a Kubo RPC API (default http://SERVER_IP:5001) and an optional token for nodes behind an authenticating proxy. Filebase’s RPC is Kubo’s, so one provider class serves both (
KuboRpcProvider, withFilebaseProviderkeeping its own messages). The node must allow the site once (API.HTTPHeaders.Access-Control-Allow-Originand-Methods), and stay online until ARIADNE’s indexer has made its copy. Saving runs a test, and a Test button runs it again: it pins a fixed 21-byte block (“ARIADNE storage test”) and checks the provider kept exactly that CID, so testing again adds nothing. The setting stays in the browser (not on the chain, not on any server); messages that asked for a key “on the publish page” now point to the profile. - Spec refs: §14.4, §16 · Tests: projects/web/test/pinata.test.ts
D-135 Getting started page and an invitation kit (owner request)
Section titled “D-135 Getting started page and an invitation kit (owner request)”- Date: 2026-10-03 · Status: accepted
- Decision: for a TestNet pilot with colleagues, the app gets a Get started page (
/<network>/start): five steps (connect a wallet; get TestNet ALGO, at least 0.3 spendable, enough for an article and a first review, with the dispenser; choose where files are stored, with the storage setting in place; link the ORCID iD, required for reviews to count toward the seal; take part: review, publish, follow, notifications), each ticked from what the account has really done (wallet, balance from algod, the browser’s storage setting, the indexer’s profile). The feed shows a welcome card pointing to it until it is hidden (per browser); the footer links it. The documentation gets “Invite colleagues” (/docs/start/invite/): what to ask the people invited to try in one or two weeks, advice (publish first, same field, ORCID early, a mix of institutions), and an invitation to copy in English and Spanish that links the Get started page. - Spec refs: §16 · Tests: projects/web/test/onboarding.test.ts; the docs build
D-136 Every author needs a verified ORCID iD for the article to hold the seal (owner, seal rule 3)
Section titled “D-136 Every author needs a verified ORCID iD for the article to hold the seal (owner, seal rule 3)”- Date: 2026-10-03 · Status: accepted (amends D-131, D-133)
- Decision: the owner asked to require a linked ORCID iD to obtain the seal. Read as: an article holds the Peer-reviewed seal only when every author (the submitter and the confirmed co-authors; declared but unconfirmed co-authors are not authors yet) has a verified ORCID iD, besides the reviews of rule 2. It closes the gap left by D-133: with an author’s verified iD, the OpenAlex co-authorship and same-employer checks run against every reviewer. When the reviews are enough but an author lacks a verified iD, the state is
awaiting_orcid(“Seal awaits ORCID iDs”), shown in amber, and the Peer review panel names the authors still without one (seal_detail.authors_without_orcid); the seal starts when the last author is verified, if that comes after the reviews; a disputed article whose authors lack an iD staysawaiting_orcid, notsuspended. Search’s “Peer-reviewed” filter and the profile counts take only sealed articles.SEAL_RULEis 3. - Spec refs: §15, §16 · Tests: projects/indexer/test/seal.test.ts, projects/web/test/peerReview.test.ts
D-137 Before signing a review: will it count toward the seal? (owner, TestNet pilot)
Section titled “D-137 Before signing a review: will it count toward the seal? (owner, TestNet pilot)”- Date: 2026-10-03 · Status: accepted (brings forward part of the “seal, second pass” of the roadmap)
- Decision: with a verified ORCID iD now required (D-133, D-136), a reviewer could easily write a review that does not count without knowing it. The review form shows “Will this review count toward the seal?” from the indexer’s own checks, not a copy: the review checks of src/seal.ts became one pure function (
judgeReview) over the data the seal pass loads (loadSealData, cached per database while the signature of the inputs is unchanged), used by the seal pass and byGET /articles/:id/seal-check/:address, which judges a review not written yet for a favourable and an unfavourable recommendation (reciprocity concerns favourable ones only). The text is checked in the browser with the same word count as the indexer (a test pins both to the same samples). Works together on OpenAlex are known only for pairs already looked up, so the form says that check happens after publishing when needed. The form also says when the article itself cannot hold the seal yet (an author without a verified iD). The review is published either way. - Spec refs: §15, §16 · Tests: projects/indexer/test/seal.test.ts, projects/web/test/peerReview.test.ts
D-138 Clean-up after the documentation: one guide, spec notes, amendments’ fields, forfeiture shown
Section titled “D-138 Clean-up after the documentation: one guide, spec notes, amendments’ fields, forfeiture shown”- Date: 2026-10-03 · Status: accepted
- Decision: (1) the publishing guide exists once, in the documentation;
spec/publishing-guide.mdstays where the specification expects it (section 14.4, D-014) as a page of pointers to the documentation and its sources. (2) Notes in the specification where it and the code differ: the size limit applies to datasets too (larger data goes to a repository such as Zenodo, referenced by DOI); the event field names of the code are the reference (article_type,from_status/to_status,claimant). (3) Section 7.5 (“an amendment’s fields SHOULD match the parent’s, enforced by the web app”) is now enforced: the publish form reads the parent article, takes its fields and locks the pickers, and refuses a parent that does not exist. (4) D-044 shown: before a retraction the panel and the confirmation give the reputation not yet claimed that it forfeits, for the submitter and for the co-authors, with the advice to claim first; the profile shows such amounts as “forfeited”. (5) Wording: a publication is at least three transactions (more with many co-authors); the Thread Score line reads “more than about N%” (ties count half). - Spec refs: §7.5, §11, §14.1, §14.4 · Tests: projects/web/test/actions.test.ts
D-139 Overlay journals on top of ARIADNE (owner request)
Section titled “D-139 Overlay journals on top of ARIADNE (owner request)”- Date: 2026-10-03 · Status: accepted
- Decision: the owner chose, among “crazy ideas”, overlay journals: curation and prestige without gatekeeping, the answer to whether ARIADNE can replace journals. A journal is a public list (D-122) with an editorial policy and a board, managed by the same notes of 0-ALGO payments to oneself, so the contract is unchanged and every change costs only the network fee. The founder marks a list as a journal with a policy (at most 600 characters, to fit a note) and invites editors (at most 10 per operation, 30 in all); nobody becomes an editor without signing an acceptance (like co-authors, D-072); an invited editor can decline, an editor can resign, the founder can dismiss editors or end the journal (it becomes a plain list). The founder and the accepted editors select articles into the founder’s list; each selection records who made it (
added_by) and notifies the article’s confirmed authors; selections stay when their editor leaves. Indexer schema v11 (lists.journal,lists.policy,list_items.added_by, tablejournal_editors);GET /journals,GET /users/:address/journals;GET /articles/:idnames the journals that selected it. Web: a Journals directory (menu and footer), the list page becomes the journal’s page (policy, editorial board, who selected each article), “Journal settings” for the founder, “Your journals” on articles and profiles for editors (select, accept, decline), “Selected by” badges on articles, and the notificationsjournal_inviteandjournal_selected(also as browser notifications). - Spec refs: §15, §16 · Tests: projects/indexer/test/journals.test.ts, public.test.ts (v10 to v11); projects/web/test/journals.test.ts
D-140 Retraction alerts through citations (owner request)
Section titled “D-140 Retraction alerts through citations (owner request)”- Date: 2026-10-03 · Status: accepted
- Decision: when an article is retracted, the articles that cite it should know. The article page of every ARIADNE article citing a retracted one (citations of D-126, any kind) shows a warning naming it and the date of the retraction (
cites_retracted), and the serve loop gives each confirmed author of a citing article that is not retracted acited_retractednotification, once (derived from the citations, so it is inserted again after a rebuild of the projections; the author of the retracted article is not notified of their own retraction). The notification leads to the citing article; browser notifications can include it. - Spec refs: §15, §16 · Tests: projects/indexer/test/journals.test.ts, projects/web/test/journals.test.ts
D-141 Volumes of overlay journals (owner request)
Section titled “D-141 Volumes of overlay journals (owner request)”- Date: 2026-10-03 · Status: accepted (amends D-139)
- Decision: the owner did not want a journal capped at 1 000 articles (the limit of a public list, which protects against lists inflated with spam) and proposed volumes, as in printed journals. A journal now has numbered volumes; one is open and receives the selections. A volume holds up to 1 000 articles and the selection that would overflow it opens the next volume by itself (deterministic: the indexer applies the notes in chain order, so every indexer agrees); the founder can open the next volume earlier, for example every year, with an optional title (at most 80 characters), and rename the open one; an empty volume is never closed; a journal can have up to 1 000 volumes. A closed volume is frozen (no additions or removals, like a printed issue; a retracted article stays and shows its retraction) and keeps its board: the founder and the accepted editors at the moment it closed. An article is selected once per journal, in the volume open at the time. A list that becomes a journal starts with volume 1 holding its articles. Indexer schema v12 (
lists.volume,list_items.volume, tablejournal_volumes); the list route takesvolume=<n>; articles name the volume that selected them. Web: the journal page shows the volumes as tabs (the open one by default), each with its dates, size and board; the founder’s settings rename the open volume or open the next; badges read “Selected by Name, vol. N (title)”. Plain lists keep their 1 000-article limit. - Spec refs: §15, §16 · Tests: projects/indexer/test/journals.test.ts, public.test.ts (v11 to v12); projects/web/test/journals.test.ts
D-142 Search engines and Google Scholar (owner request, after an outside review)
Section titled “D-142 Search engines and Google Scholar (owner request, after an outside review)”- Date: 2026-10-03 · Status: accepted
- Decision: another AI could not see ariadne.press: the bare address answered with a page that redirected in the browser (“Opening ARIADNE…”, no link), so crawlers never reached the articles; article pages were rendered on the server but all titled “ARIADNE”, with no description, no Google Scholar tags and no link preview; robots.txt and sitemap.xml were HTML pages. Now:
/redirects on the server (307) to the network remembered in a cookie (ariadne_network, set with the localStorage key when a person switches network) or the default one (this also removes a latent loop for MainNet, whose home is/mainnetthere); every page has its own title (“· ARIADNE TestNet”); article pages carry their description (the abstract), canonical address, Open Graph and Twitter card (a preview image, public/og-image.png from scripts/og-image.mjs), the Highwire citation_*tags Google Scholar reads (title, authors as “Family, Given” from verified ORCID records, publication and online dates, publisher, DOI when verified, abstract and full-text URL, keywords, and journal title and volume when an overlay journal selected the article) and schema.org ScholarlyArticle JSON-LD (escaped so that user text cannot close the script element); journal, list and profile pages have their name, description and canonical address; search results, the inbox and /me say noindex; robots.txt is real (everything but /api/, with the app’s and the documentation’s sitemaps) and sitemap.xml lists the home, journals, about page, every article and every journal of the networks that may be indexed. Which networks: MainNet only by default (ARIADNE_INDEXABLE_NETWORKS), because TestNet articles are practice; TestNet pages say noindex, follow. The documentation is indexed (Starlight’s own sitemap and tags). Not done: a PDF of each article (Scholar prefers one), to consider for MainNet. - Spec refs: §16 · Tests: projects/web/test/scholar.test.ts
D-143 PDF and print from the browser, not from the server (owner decision)
Section titled “D-143 PDF and print from the browser, not from the server (owner decision)”- Date: 2026-10-03 · Status: accepted
- Decision: after D-142 the owner asked whether a PDF of each article was needed and what it would cost. Google Scholar indexes the HTML full text when the page carries the
citation_*tags (D-142), so a PDF is not required; readers want to download, print and archive. A server-side renderer (headless Chromium, one render per version cached by CID, a few hundred MB of memory while rendering, no IPFS storage) stays possible later but adds a service to run on a small server; PDFs supplied by authors were rejected as the article’s format: a thousand layouts, no way to check they say what the signed Markdown says, and duplicated storage (a PDF can still be supplementary material inassets/). Chosen: a print stylesheet and a “PDF / Print” button on every article; the reader’s browser makes the PDF (“Save as PDF”). The printed copy shows the article alone in a reading layout (serif text, no app around it, figures, tables, code and formulas kept whole, link addresses written out) headed by a record: version and date, address, CID, DOI and licence when present, the seal’s state and counts, a TestNet warning off MainNet, and the APA reference of that version. No server cost, one uniform format. - Spec refs: §16 · Tests: projects/web/test/print.test.ts
D-144 Printing a journal volume, in editing or finished (owner decision)
Section titled “D-144 Printing a journal volume, in editing or finished (owner decision)”- Date: 2026-10-03 · Status: accepted
- Decision: the owner asked that the volumes of overlay journals (D-141) be printable with a cover giving the journal’s and the volume’s data, followed by all their contents, and then that open volumes print too as long as the cover gives their state. A print view per volume (
/u/<owner>/lists/<id>/print?volume=N, not indexed), printed or saved as PDF by the reader’s browser as articles are (D-143): a cover (journal name and description, volume number and title, state “In editing” while open or “Finished” once closed, with dates and what the state means for the copy, article count, address, date printed, a TestNet warning off MainNet, the editorial policy, and who edited it: the board snapshot of a finished volume, the founder and accepted editors of one in editing), the contents numbered in selection order, then every article in full from a new page with its printed record (D-143, the network said once on the cover) and its APA reference. The volume’s states are worded “in editing” and “finished” on the journal’s page too. A volume of more than 50 articles prints in parts of 50, so that one page never fetches and renders a thousand bodies. No server rendering or storage. - Spec refs: §16 · Tests: projects/web/test/volumes.test.ts
D-145 Quality of life: eight reading, writing and reviewing aids (owner decision)
Section titled “D-145 Quality of life: eight reading, writing and reviewing aids (owner decision)”- Date: 2026-10-03 · Status: accepted
- Decision: from a list of quality-of-life ideas the owner chose eight, all in the web app (no contract or indexer change). (1) Before you sign: a box in step 3 of publishing and before a new version lists what cannot be fixed afterwards: title or abstract still the template’s, empty abstract, no keywords or licence, template example text left in, files linked but missing, empty links or links outside the folder, external images (shown as links), images without description, formulas KaTeX cannot typeset, references without DOI or link, a long text without headings, links to ARIADNE articles that do not exist, and authors without a verified ORCID iD (seal rule 3); warnings only (src/lib/prepublish.ts). (2) What changed between versions: /a/
/changes?from=&to= compares index.md line by line (Myers) and word by word, and lists the assets added, removed or changed from the directory blocks alone (fetched as raw blocks, checked against their CIDs, decoded without fetching the files); linked from the version number, the Versions list and each review written on an earlier version (“on v1 · what changed since”). (3) Previews of linked ARIADNE articles on hover or keyboard focus (the indexer’s citation rule for what a link to an article is). (4) Copy buttons on code blocks, a click on a formula copies its LaTeX, figures open full size. (5) Quote in review / Quote in comment from a selection in the article, formulas quoted as LaTeX. (6) Reply point by point for authors: every point of a review quoted with a Response line under each. (7) Wallet and transaction errors in plain words (cancelled, pending, expired, duplicate, offline, low funds with the amount missing, the contract’s assert messages from the ARC-56 source map in plain words), the raw error under Technical details. (8) Share with a QR code (made in the browser) on articles, profiles, lists and journals, and the phone’s share sheet where there is one. - Spec refs: §16 · Tests: projects/web/test/qol.test.ts
D-146 Writing comments and reviews: preview, formula check, formatting help (owner decision)
Section titled “D-146 Writing comments and reviews: preview, formula check, formatting help (owner decision)”- Date: 2026-10-03 · Status: accepted
- Decision: the owner asked whether comments should have Markdown and LaTeX. They had both since the start (the same rendering as articles, images excepted, raw HTML sanitised), but the box was plain and nothing showed what the text would look like, while a comment or review can never be edited. Chosen: no editor toolbar nor WYSIWYG (researchers know Markdown and LaTeX; more weight and bugs), images and HTML stay out; instead every comment, reply and review box gets a live preview under it (the same rendering as the page; the choice remembered in this browser), a warning for formulas KaTeX cannot typeset (the check of D-145), a warning past the 256 KiB a comment can hold, and a folded formatting help (src/components/MarkdownEditor.tsx).
- Spec refs: §16 · Tests: projects/web/test/qol.test.ts
D-147 The profile’s figures: twelve equal cards with icons (owner decision)
Section titled “D-147 The profile’s figures: twelve equal cards with icons (owner decision)”- Date: 2026-10-03 · Status: accepted
- Decision: the owner asked for the row of figures on a profile to be symmetric, each with its own icon. The profile always shows the same twelve figures (reputation, fields, publications, reviews, comments, citations of all works on OpenAlex, peer-reviewed articles, reviews that count, cited on ARIADNE, Thread Score, followers, following), a dash with an explanation when one is unknown (no verified ORCID iD, no article that counts yet), in equal cards on a grid of 6, 4, 3 or 2 columns by the width available (a container query): twelve fills every row, so no card is left alone. Each card has a line icon in the accent colour; the Thread Score’s is a spiral, Ariadne’s thread wound.
- Spec refs: §16 · Tests: none (layout)
D-148 Following articles, inbox filters, where the reader left off, related articles (owner decision)
Section titled “D-148 Following articles, inbox filters, where the reader left off, related articles (owner decision)”- Date: 2026-10-03 · Status: accepted
- Decision: four more quality-of-life items chosen by the owner. (1) Following an article: recorded like public lists (D-122), a note of a 0-ALGO payment to oneself with the op
{"op":"watch","articles":[id],"on":bool}, so no contract change, no deposit, cross-device and public; the indexer (schema v13, projection tablewatches, at most 500 articles per address, replayed in chain order by rebuild) notifies the followers of a new version (watched_version), a status change by the author, flags or governance (watched_status), a review (watched_review) or a comment (watched_comment), except the actor and whoever is told in their own right (authors, the replied-to, the mentioned);GET /users/:address/watchingandwatchingin the viewer state; Follow on the article page; browser notifications for the four kinds. (2) Inbox filters by kind (reviews, comments and replies, mentions, articles I follow, journals, co-authors and followers, retractions) with unread counts, Unread only and Mark these read, and the list of followed articles. (3) A reading bar and “Continue where you left off” (the section and percentage), kept in this browser only per network and article, for the same version, 90 days, forgotten at the end. (4) Related on ARIADNE after an article: those that cite it, those it cites, others of its field by score (not retracted), each once. The documentation also explains, in What ARIADNE is and How it works, that ARIADNE is three parts: the protocol (the contract and the texts on IPFS, with the note conventions), the indexer and the web app. - Spec refs: §15, §16 · Tests: projects/indexer/test/watches.test.ts, projects/web/test/following.test.ts
D-149 Principles and commitments, a draft manifesto (owner decision)
Section titled “D-149 Principles and commitments, a draft manifesto (owner decision)”- Date: 2026-10-04 · Status: draft
- Decision: an outside review argued that removing the editor moves editorial power into the rules, and proposed a document stating which principles ARIADNE protects and what it gives up when they conflict. The owner adopted the idea. The documentation gains /docs/start/principles/ (draft 0.1): the purpose (keep apart making an idea public, judging it and crediting it), eight principles (free reading, publishing without permission, a permanent record with a revisable interpretation, recognition earned by field from others, public rules, no operator needed, figures that describe and do not rank people in the spirit of DORA and the Leiden Manifesto, minimal personal data), seven tensions each with what is chosen, what it costs and how the cost is limited (openness and abuse; permanence, correction and erasure; immutability and fixing mistakes; transparency and privacy; past recognition and present judgement; autonomy and governance; useful figures and their incentives), red lines, and how the page changes. Four points are marked to decide before MainNet (legal requests, delayed-identity reviews, vote weight from recent reputation, jury governance). The review’s precautions go to the roadmap: simulate reputation and attacks, responsible figures in the interface, vulnerability disclosure, governance key loss, a takedown list if adopted (before MainNet); coordinated-voting detection, an “at a glance” box per article, an independence drill (after).
- Spec refs: §1, §16 · Tests: none (documentation)
D-150 The four open points of the principles, decided; the reputation simulation (owner decision)
Section titled “D-150 The four open points of the principles, decided; the reputation simulation (owner decision)”- Date: 2026-10-04 · Status: accepted (the contract parts take effect with the MainNet contract v4)
- Decision: the owner accepted the proposals for the four points D-149 left open. (1) Lawful requests: we stop serving the content from our indexer, gateway and website and add it to a public list of what we no longer serve (date, reason, not the content); the record is untouched; disagreement is answered with reviews, comments and flags, not takedowns. (2) No delayed-identity reviews: the sender of a review is public on the chain at once, so hiding it in the interface would only pretend to protect, and real protection needs cryptography ARIADNE lacks; a reviewer who wants distance can use another address, whose review does not count toward the seal; revisit with the pilot’s evidence. (3) Vote weight:
research/reputation-sim(ten years of one field, eight seeds, five rules, four scenarios) found that weighing by recent reputation changes little (one or two points), that ten fresh addresses voting for each other reach the maximum reputation the daily limit allows (1.15 times the best honest researcher) and can dispute any article within a week for about 5 ALGO, and that damping repeated votes (the n-th vote from a voter for the same author counts 1/n) keeps such a ring at 2% of the best researcher, cuts the founders’ share of influence from 14% to 9% and lifts a contrarian result from the 64th to the 91st percentile, while a cartel of established researchers still gains (1.30x). So reputation stays lifetime, and the MainNet contract will damp repeated votes for articles, reviews and comments, make false flags cost credibility and clear unresolved disputes after a set time. (4) Disputes are settled by a qualified jury: seven jurors drawn verifiably at random among those who pass the seal’s tests and have no conflict with authors or flaggers, signed public answers within 14 days, two thirds of at least five deciding; in a first phase governance executes the verdict and publishes the tally (or decides with public reasons without quorum); later the contract takes the verdict. The principles page becomes version 1.0; the report is published as /docs/reference/reputation-simulation/. - Spec refs: §10, §12, §16 · Tests: research/reputation-sim/sim.ts (deterministic seeds)
D-151 The jury for disputes, phase 1, with every case decided (owner decision)
Section titled “D-151 The jury for disputes, phase 1, with every case decided (owner decision)”- Date: 2026-10-04 · Status: accepted
- Decision: the owner asked for the jury of D-150 to be built with every possibility decided. The indexer opens a case when an article becomes disputed and draws the jury from the seed of the Algorand block 8 rounds after the flag that crossed the threshold (unknowable when flagging): eligible people (the seal’s tests: verified ORCID iD, reputation 10 in the primary or secondary field or 3 OpenAlex works in the area, no conflict with any author or flagger; not an author declared or confirmed, not a flagger of the round, not governance, not a juror of an earlier round or attempt for the article; one seat per ORCID iD) ordered by SHA-256(“seed|article|round|attempt|address”); 7 seats, the rest alternates; the whole order published. A field with fewer than 7 widens to its area, then to everyone qualified; fewer than 5 anywhere: no jury, governance decides alone with public reasons. Jurors answer clear or retract with reasons (at most 600 characters) or recuse (the next alternate is seated at once), as list notes
{"op":"verdict","article","round","answer","reasons"}; the first answer is final; late answers, answers after a seat passed on, second answers and answers without a seat are kept but not counted. After 7 days silent seats pass to alternates. The outcome: two thirds of at least 5 answers (early when one side holds two thirds of the seats); at the 14-day deadline a quorum without two thirds is “no consensus” and clears; fewer than 5 answers draws a second jury once, and a second failure clears. Governance carries out the outcome within 7 days; the case shows executed, contradicted, before the verdict, or overdue. Schema v14 (base tables jury_cases, jury_seats, jury_answers); notifications jury_summons and jury_verdict; GET /articles/:id/jury, /users/:address/jury; the dispute page /a//dispute with the juror’s form. - Spec refs: §9, §15, §16 · Tests: projects/indexer/test/jury.test.ts, projects/web/test/jury.test.ts
D-152 What this service no longer shows: notices and a transparency report (owner decision)
Section titled “D-152 What this service no longer shows: notices and a transparency report (owner decision)”- Date: 2026-10-04 · Status: accepted
- Decision: the owner asked whether a list of withheld content contradicts the project. Answer adopted: the protocol cannot be censored (the chain, other indexers, anyone’s copies), but this service is subject to law, and the honest way to comply is to say so; a silent removal would contradict the principles, a public index of removed content would point to it. So: the list lives in the repository (spec/withheld.json: id, network, kind, article, seq, date, reason in personal-data, unlawful-content, court-order, copyright, and the basis), read by the indexer at start; the API takes out the text, title, abstract, snippets and CIDs of what is withheld and puts a
withheldnotice in its place (one mask over every response); its CIDs are no longer pinned and the gateway’s Kubo deny list (written by the indexer into a volume Kubo reads) answers them 410 Gone; the website shows the notice in place of the article, review or comment, and a transparency report page per network (footer link) lists the cases by record and reason, never the content or the CID. Only lawful requests; disagreement is answered with reviews, comments and flags. - Spec refs: §15, §16 · Tests: projects/indexer/test/withheld.test.ts
D-153 The sandbox: a private test network for invited testers (owner decision)
Section titled “D-153 The sandbox: a private test network for invited testers (owner decision)”- Date: 2026-10-05 · Status: accepted
- Decision: the owner asked for a test mode, for people he chooses, in which articles, votes, lists, reputation and the seal can be tried without anything being on chain. Proposed and accepted instead: a private chain, so that what is tested is the real thing. The sandbox is a fourth network (
sandboxin the manifest, between TestNet and LocalNet) served by production but only to invited testers. (1) Chain: an algod in development mode inside the hosting stack (algod-sandbox, the LocalNet image, profilesandbox, no port, tokens generated on the server; the image’s start script prints its tokens, so a wrapper drops those lines from the log). The web app deploys the same contract on it at start (a new governance key, the application funded, the 42 fields), writes the sandbox’s manifest entry into a shared volume, and deploys a new application on every reset; the indexer (indexer-sandbox) waits for the manifest, stops when it names another application (Docker starts it again) and keeps one database per chain and application. (2) Access: invitations by name with codes of their own (revocable) and the owner’s admin code from the server’s environment; the invitation link sets a signed cookie for 180 days;proxy.tsanswers 404 to every sandbox page and API request without it, and the manifest leaves the sandbox out for anyone else. (3) The browser reaches algod (reads, simulate, send; never the development-mode or administration routes), the indexer and the content only through gated routes of the web app; content is kept on the server only, never on IPFS. (4) Wallet: test accounts made in the browser (use-wallet’s mnemonic provider, keys kept in the browser), funded from a dispenser exported once from the node’s KMD. (5) Time: algod’s development mode stamps each block with the previous block’s time plus a set offset, so the web app keeps “ahead” (how far the sandbox’s clock runs ahead of the wall clock) and sets the offset every second so that the next block lands on wall clock + ahead, making a block when the chain is idle for two minutes; moving the clock raises “ahead” and makes a block; the indexer and the pages’ relative dates read “ahead”. (6) Identities: the sandbox’s indexer takes any well-formed declared ORCID iD as verified at once (a made-up name, ORCID never asked) and asks OpenAlex nothing. (7) Tools, always through the contract’s own methods, by helper accounts the server holds (fresh where the rules require, never authors, jurors never flaggers): votes for an article; reputation (the tester’s account reviews the field’s host article, signed in the browser, and helpers vote it useful; the clock moves a day when the daily limit holds the grants); the seal (two helpers with test iDs and reputation 10 write favourable reviews of 200 words or more, test iDs declared for the tester’s authors); a dispute with a jury (14 helpers qualify first, because the indexer draws from those qualified when the seed block exists, then others flag to 30, then blocks for the seed), juror answers (all retract, all clear, 4 to 3, only three), governance’s resolution (the sandbox holds the key), more people for a second jury; helpers publishing, reviewing and voting articles; the clock; test ALGO; reset and invitations for admins; a log of what testers did. (8) Interface: a permanent purple SANDBOX band with the clock; a “Sandbox tools” button in the bottom-right corner of every sandbox page with the tools that fit the page (owner’s suggestion); the Test tools page with all of them; a note in the ORCID form. Nothing carries over to TestNet or MainNet. - Spec refs: §3.2, §15, §16 · Tests: projects/web/test/sandbox.test.ts, projects/indexer/test/sandbox.test.ts
D-154 Sandbox: problem reports from testers (owner decision)
Section titled “D-154 Sandbox: problem reports from testers (owner decision)”- Date: 2026-10-05 · Status: accepted
- Decision: from the list of sandbox improvements the owner chose problem reports, so that invited testers’ findings reach him with what is needed to reproduce them. “Report a problem” in the floating Sandbox tools panel: the tester describes what they did, expected and saw; the report carries the page, the account in use, the last 15 transactions the page sent through the app’s transaction flow (useTx: ids, round, or the error) and the last 15 errors (window errors, unhandled rejections, console.error), recorded only on sandbox pages, plus browser and screen size; the server adds the application, round, clock and the tester’s tools of the last 15 minutes. “Show what is sent” shows it all before sending. Stored in the sandbox’s database (text at most 4 000 characters, context at most 24 000, 30 reports per tester per hour; the reporter is a hash of the invitation code, never the code). The Test tools page lists a tester’s own reports with their status, and every report for admins, who mark them resolved or open with a note and copy the open ones as Markdown; the banner shows admins the number of open reports.
- Spec refs: §16 · Tests: projects/web/test/sandbox.test.ts
D-155 Sandbox audit: fixes and the tools that were missing (owner decision)
Section titled “D-155 Sandbox audit: fixes and the tools that were missing (owner decision)”- Date: 2026-10-06 · Status: accepted
- Decision: an agent (Claude) audited the sandbox on a local copy, as admin and as tester with two accounts, and the owner asked for every finding to be fixed and the audit run again. Fixed: (1) after a confirmed transaction the page refreshes and the layout handed the wallet a new network object; a new wallet manager was built and never resumed (use-wallet resumes once), so the next signature failed (“Cannot read properties of null”); the manager is now built from the network’s values, not the object (all networks); (2) “Governance retracts” lacked the fee for its inner transaction (the article token’s revocation); (3) “More people for a second jury” picked helpers who had already been drawn; it now excludes everyone in the article’s jury cases; (4) a page left open across a reset signed for the old application; open pages ask every 20 s (and on focus) which application runs, show “The sandbox was started again by
” and block signing; (5) after the clock moves, ten blocks follow in the background once the indexer has read the new time, so a draw that waits for 8 rounds starts within a minute (it took ~16); (6) the sandbox’s own words: getting started (test accounts, test ALGO, no storage to set up, test iDs), the welcome card, donations, the storage panel, the publish form’s provider, no “ipfs pin add” line; (7) a visit to TestNet dropped the test account: the account last used is connected again, and the invitation link remembers the sandbox as the chosen network; (8) DOIs: in trust mode the indexer takes well-formed DOIs as verified and asks nobody outside (it used to query the real Zenodo), with “Make up a test DOI” in the form; (9) “Give the seal” waits for the authors’ new test iDs to be verified before reporting; (13) the status page no longer calls an idle sandbox degraded; (14) test accounts can be renamed; (15) names: helpers get made-up names (“Helper Ana Ruiz”) and a tester’s test identity shows the account’s name, from a file the web app writes next to the manifest that the indexer reads (identities and DOIs looked at again every minute in trust mode); (16) messages: blocks made, jurors seated now; (17) the seal check offers “Declare a test ORCID iD now”. Added tools: a helper comments, replies to the latest non-helper comment, or mentions the tester; a seat on a jury for the tester’s account (a dispute that qualifies the account first, then helpers ahead of it in the published order recuse until it is seated; or “Put me on the jury”); journals with helpers (a helper founds a journal and invites the tester; helpers invited by the tester accept; a helper editor selects an article; helper addresses to invite). The second audit also found and fixed: the dispute page and the article’s notice showed round n+1 (the contract counts rounds from 1), and the article page fetched juries for every article; a journal invitation in the inbox read “Article 0” and led to the founder’s profile instead of one’s own journals where Accept is; the start page’s subtitle; “already in this journal” for a repeated selection. Not changed: inboxes and journal lists show addresses (or NFD names), not ORCID names, on every network. - Spec refs: §9, §15, §16 · Tests: projects/web/test/sandbox.test.ts, projects/web/test/journals.test.ts, projects/indexer/test/sandbox.test.ts
D-156 Accessibility, ergonomics and order pass (owner decision)
Section titled “D-156 Accessibility, ergonomics and order pass (owner decision)”- Date: 2026-10-06 · Status: accepted
- Decision: an agent (Claude) reviewed the web app on the sandbox for accessibility (WCAG 2.2 AA), ergonomics and order, with automated checks at 1280 and 375 px in both themes plus keyboard tests, and the owner asked for all fourteen findings to be fixed. Fixed: (1) every visible label is tied to its field (publish, review, comment, flag, status, CID, storage provider); the review and comment editors have names; (2) links inside running text are underlined (colour alone told them apart); (3) the dark theme’s “low contrast” buttons were a measurement taken during the buttons’ 0.15 s colour transition: measured after it, every text passes, so nothing changed; (4) the sandbox panel: its button comes first in the document, so Tab goes from it into the panel (CSS shows the panel above), Escape and Close return focus to the button, the panel has a heading and its select a name; (5) the wallet and account lists under the header buttons were announced as ARIA menus without the arrow keys a menu promises: they are plain lists of links and buttons now (a disclosure), Escape returns to the button, and a click or Tab outside closes them; (6) headings inside a review or comment start at h3 under “Reviews” or “Comments”, whatever level the author used (## or ###), so the outline never skips; (7) article titles in the feed, citations and lists are headings (h2), with the same look, so people can move from article to article; (8) every target is at least 24 px: addresses, footer links, field tags, the brand on phones, “Test tools”, summaries and filter checkboxes; a profile’s figures make the whole card the link; (9) a profile’s title is the person’s NFD name or verified ORCID name when known, the address below it; (10) the Test tools page has an index and four groups (You and time, An article, Other people, The sandbox), with panels side by side on wide screens; (11) the sandbox panel’s article tools are under sub-headings (Votes and seal, Dispute, Comments, Jurors answer, Governance resolves, The jury); (12) the sandbox band is one line on phones (119 px before, 40 now; the sentence stays for screen readers); (13) the footer keeps room below it for the floating button; (14) “Connecting…” under the article’s actions reads “Getting your account ready…” and is announced politely. Also: the sandbox tools’ failures use the plain words of D-145 (an account without ALGO is told to use “+100 ALGO”), and a test account’s state no longer differs between the server’s page and the browser’s (a hydration warning on the Test tools page). Not changed: the order of the article page (actions, body, versions, peer review, reviews, comments) and the header’s entries were found sound.
- Spec refs: §16 · Tests: projects/web/test/markdown.test.ts
D-157 Security audit of the web and the server: every finding fixed (owner decision)
Section titled “D-157 Security audit of the web and the server: every finding fixed (owner decision)”- Date: 2026-10-06 · Status: accepted
- Decision: four reviews by agents (Claude): the host over SSH, read only; the web app’s server side; the browser side and the response headers; the indexer and the deployment. About 90 harmless requests went to production, and nothing was exploited. The owner asked for everything to be fixed. Host (the owner ran the commands): SSH accepted passwords (the cloud image’s 50-cloud-init.conf won over 60-cloudimg-settings.conf), with about 13 000 guesses a day; a 10-ariadne.conf now allows keys only and turns X11 forwarding off. The only two password logins were the setup on 2026-10-01, from the IP that later used the key, so there was no sign of intrusion. 2 GB of swap were added and the kernel update applied with a reboot. Indexer: markdownToText (search text, seal word counts, also inside /search/posts) had quadratic regexes: 40 000 “[” took 2.7 s, so one 1 MiB comment could freeze the single-threaded indexer at every restart. It is linear now: input cut to 4x the kept length, classes exclude the delimiter that opens the next attempt, lengths bounded, comments removed with indexOf. The same was fixed in references, front matter, DOI and ORCID URL trimming, with 31 hostile 1 MiB inputs tested. The regex search budget is per client (first X-Forwarded-For address, a /64 for IPv6; 4 s a minute, then 429), within the global 20 s, with 1 s per search; list parameters are capped at 20 values (400 beyond); a search stopped by its timeout no longer leaves its statement open. Withheld content no longer leaks through titles in profiles and notifications, the current CID in content_record, text/keyword/CID searches or /search/suggest, and Kubo garbage-collects after unpinning (retried hourly if it fails). The API answers through a query_only connection and sends nosniff. A Kubo RPC token is supported (ARIADNE_KUBO_API_TOKEN). Web: sandbox and dev content was served from the app’s origin with SVG as image/svg+xml and no policy, so a tester’s SVG opened by the owner could have created an admin invitation and read keys in localStorage. These responses now carry the gateway’s policy (CSP default-src ‘none’ … sandbox), and anything but plain images and text is a download. The reading time on every article page (and the review form’s word count) had the same quadratic regexes on the web server (40 000 “<” took 1.5 s per view); the review form now uses an exact copy of the indexer’s linear function, and the reading time linear patterns. The version comparison skips texts over 20 000 lines and word diffs over 4 000 characters, since an author could have made Myers’ search run for minutes. Other web fixes: the Zenodo reading copy’s link pattern is bounded. Push subscriptions: capped body reads, a per-client limit, keys that must be a real P-256 point and a 16-byte secret, and a first notification the push service must accept before a new subscription is kept, so made-up endpoints cannot fill the table. The sandbox tools: helpers 1 to 10 at a time and 300 in all, the clock at most five years ahead, names.json at most 5 000 entries, helpers keep their names, the account query and the problem report’s page validated, the request body capped. The algod proxy: no “.” or “..” segments, anchored paths, same-site check and capped body on writes. Dev pin: same-site check and capped body. NFD and avatar routes are rate-limited per client. The sandbox gate decodes the path (“%73andbox” was served by the avatar route) and routes that take the network from a JSON body see the sandbox only for testers. Images whose path resolves outside the article’s folder (“%2e%2e”) are not embedded. Formulas cannot paint outside a text’s box (overflow: clip). Provider keys are kept for the tab unless “Remember on this device” is ticked. Problem reports drop anything that looks like a mnemonic or a key. KaTeX is 0.19 (advisory), ws and bn.js updated within their majors (9 moderate WalletConnect v1 advisories remain, no upstream fix). No X-Powered-By. A content security policy with a nonce is sent Report-Only, with violations logged by /api/csp-report, to be enforced once real wallet use is clean. Deployment: HSTS (subdomains included), nosniff and Referrer-Policy on every host; on the app, X-Frame-Options SAMEORIGIN, a base CSP (frame-ancestors, object-src, base-uri, form-action), Permissions-Policy and COOP same-origin-allow-popups. Request bodies are capped (1 MB, 42 MB for dev pin, 16 KB on the API and gateway), and the global notifications feed is closed to the public. deploy/.env was visible inside web and both indexers through a mount of deploy/; the server now mounts nothing of it, and the local rehearsal uses docker-compose.local.yml. Two networks: edge, plus a sandbox network without internet access that only the web app joins (Caddy and the public indexers no longer reach algod or the sandbox indexer). Kubo’s RPC requires a bearer token (API.Authorizations; the healthcheck uses it). Every container has no-new-privileges, a memory ceiling, pids_limit 256 and rotated logs. Capabilities are dropped where possible: web and indexers keep none, Caddy and docs only NET_BIND_SERVICE (the Caddy binary carries it as a file capability and will not start without it in the bounding set, as the rehearsal showed). The sandbox indexer gets no ORCID credentials. .dockerignore and deploy/.gitignore cover any *.env and Notepad’s .txt copies. The whole configuration was rehearsed locally (sandbox profile: services healthy, Kubo 403 without its token, isolation, headers, limits) before it went to the server. Not changed: the sandbox cookie keeps the invitation code (HttpOnly, signed; its HMAC and the Zenodo cookie’s AES keys are derived differently). Pending (ROADMAP): enforcing the CSP, and backups (owner’s choice).
- Spec refs: §14.4, §15, §16, §18 · Tests: projects/web/test/security.test.ts, projects/web/test/linear.test.ts, projects/web/test/sandbox.test.ts, projects/web/test/markdown.test.ts, projects/indexer/test/linear.test.ts, projects/indexer/test/withheld.test.ts, projects/indexer/test/search.test.ts, projects/indexer/test/api.test.ts, projects/indexer/test/public.test.ts
D-158 User journeys: every finding of three testers fixed (owner decision)
Section titled “D-158 User journeys: every finding of three testers fixed (owner decision)”- Date: 2026-10-06 · Status: accepted
- Decision: three agents (Claude) played an author, a reader who becomes a reviewer, and a juror and journal editor on a local sandbox (production build), each with its own invitation and browser identity, and reported what failed or confused; the owner asked for everything to be fixed. Reading and publishing: the article page shows the abstract, keywords (linked to search) and licence of the front matter; identifiers (ASA, CID) move into “Technical details”; badges show once; a just-published article says it is being read and refreshes itself. Publishing has a preview of the article as readers will see it, a labelled Markdown box, plain wording (exact terms under “Technical details”), field names as YAML comments next to the field codes (both parsers accept them), and links to other articles written as “/sandbox/a/2”, “/testnet/a/2” or “/a/2” are recognised as articles, rendered as app links and counted as citations. Reviews and the seal: the form shows every requirement at once as a checklist, the recommendation starts empty and is required, “Publish the review” replaces “Pin and submit review”, and quoted lines (starting with “>”) no longer count toward the 200 words, in the web and the indexer alike (SEAL_RULE 4, seals recomputed); the seal panel says “being checked” instead of “No review yet” or an IPFS message on the sandbox, refreshes itself while something is pending, and words versions correctly. Test DOIs use the 10.5072 prefix and show as “test DOI” without a doi.org link. Printed articles and volumes name each network (Sandbox, LocalNet), a retraction’s real cause (authors, or governance after a jury) and its order relative to the selection, and the same seal words as the page. Citations: an author without a name is the short address; RIS uses UNPB for preprints. The reading toast needs 3 minutes of reading. Names: one rule everywhere a person appears (NFD name, else the verified identity name from the indexer’s new GET /names, else the short address); mentions are found by name (people search now ignores accents and case) and shown by name; the sandbox no longer invents “Sandbox Researcher NNNN” or prefixes helpers with “Helper”, and a test iD takes the test account’s name. Notifications (indexer schema 15): new kinds coauthor_confirmed, review_useful, reviewed_version, disputed (system, with the flag weight), dispute_resolved (with the outcome), journal_editor_accepted, journal_editor_selected, seal_granted, doi_verified; the jury verdict says its outcome; one notification per person and action (reply > mention > comment on my article > followed article); journal notices carry the journal’s name; links go to the review (#r), comment (#c) or dispute round; unread counts are computed one way in the bell, the inbox and the profile; targets scroll clear of the sticky header and are highlighted; push messages use the same sentences. Disputes: a flag may carry a note of up to 500 characters, sent as a “flag-note” list-note op in the same atomic group as the flag call and kept with the flag; after flagging the page says where the round stands (weight of 30) and what a round is; flagging that needs more reputation says so; the dispute page keeps every round’s flags (reason, note, weight) after resolution, shows each seat’s “answer by” date and state, future times as dates (“in 7 days”), refreshes itself while a case is open, and a cleared article says it was disputed and cleared. Article actions: a claimed reputation stays hidden until the indexer catches up; an invited co-author first sees only “Confirm co-authorship / I am not a co-author” (the review form waits too); the vote panel says the account’s weight; cost lines are plain, with protocol terms under “Technical details”. Journals and lists: retracted articles are not offered for selection; /journals has “Start a journal” (one signature); accepted editors get select and take-out controls on the journal page; “+ New list” under public lists is public; a list being made public stays visible; journal invitations are one confirm step. Following: the feed has a “Following” tab. Shell and sandbox: no “Connect a wallet” flash while the account is being found (useChain().loading; a placeholder of the same size in the header); on the sandbox the words are “Choose a test account”; the account menu lists this browser’s test accounts and announces switches; the sandbox panel closes on page change, shows progress and results at its top, offers only tools that apply and follows the jury’s outcome for governance; the tools accept journal URLs, log plain names and label each account’s buttons; generated articles sit in fitting fields, host articles read as ordinary test articles; on phones the tools button is a small round button at the end of the tab bar, which gains Journals, and the notifications note takes one line. Copy buttons say “Copied” or “Copy failed: select the text”. Verified on the local sandbox after the integration (schema 14 to 15 migration on the testers’ data, a flag with a note through the chain and back, the inbox, the Following tab, a phone view).
- Spec refs: §9, §11, §13, §15, §16 · Tests: projects/web/test/d158-reading.test.ts, projects/web/test/d158-people.test.ts, projects/web/test/d158-disputes.test.ts, projects/web/test/d158-shell.test.ts, projects/indexer/test/notices.test.ts, projects/indexer/test/public.test.ts
D-159 A “Papyrus” theme (owner decision)
Section titled “D-159 A “Papyrus” theme (owner decision)”- Date: 2026-10-06 · Status: accepted
- Decision: the owner asked for a new theme, pastel brown, legible, like papyrus. The theme switch (D-099) offers System, Light, Dark and Papyrus; Papyrus is remembered in this browser like the others (localStorage ariadne.theme, applied before the first paint). Palette: parchment background #f4ead3, sidebar #eadcbd, panels #f9f2e2, sepia ink #33281c, secondary text #64523c, rust-brown links #8a4517, a softer green for votes, red and amber tuned to the paper; every text pair meets WCAG AA (body 12:1, secondary text above 5.5:1, links above 5.2:1; a test checks the pairs from the CSS) and a scan of four pages found no text below the minimum. Articles, reviews, comments and page titles use a system book face (Iowan Old Style, Palatino, Charter, Georgia) only in this theme; the interface keeps the system font. The system’s dark palette never overrides a chosen Papyrus. The switch shows its four choices two by two in the sidebar and in one row in the footer. Print is unchanged (black on white).
- Spec refs: §16 · Tests: projects/web/test/people.test.ts
D-160 Performance: every finding of the audit fixed (owner decision)
Section titled “D-160 Performance: every finding of the audit fixed (owner decision)”- Date: 2026-10-07 · Status: accepted
- Decision: agents (Claude) audited performance: the web’s bundles and server rendering, and the indexer on a synthetic database of 20 000 articles, 20 000 reviews, 40 000 comments, 184 000 votes and 50 000 notifications; production was measured from inside the server (the owner’s link was losing packets that day). The owner asked for everything to be fixed. Indexer (schema 16): the HTTP API runs in a worker thread with its own read-only connection, so passes no longer stall it (a 1 ms route went from a p50 of 4.7 s while passes ran to 1.8 ms); passes are incremental (SQL triggers fill work queues and change counters bumped only when a result changes; content rechecked every 30 days, fetched_at untouched when unchanged; word counts stored per text; one cached copy of the seal data; sandbox identities rechecked only when the names file changes), idle cost from about 4.9 s to 43 ms per cycle; new indexes and PRAGMA optimize (at start and daily, ANALYZE once after the migration); search rewritten (dirty queues, two-phase fetch, shared facet scan cached by data generation, facets=0, post search from the matches, cited counts stored, keyword and people tables for suggestions): text search from 10-120 s to 0.07-0.5 s, suggestions from 0.8 s to 25 ms per keystroke; Thread Score computed daily in 7-day age bins (linear; 8.3 s to 0.35 s at 100 000 articles; tested against the exact pairwise reference); article and profile lists cut at defaults (100 reviews and 300 comments; 200 publications, reviews and comments) with counts, has_more, paged routes and keyset cursors; notifications take after= (echoed) for increments; public answers carry Cache-Control max-age and weak ETags (304), with an in-memory response cache; peak memory from 433 MB to 294 MB. Wildcards over every article body alone still hit the 1 s pattern limit (D-157), by design. Web: the contract client loads only when an action is prepared or signed, and the browser copy of the ARC-56 has no TEAL source (scripts/patch-client.mjs writes ariadneClient.ts for the browser and keeps ariadne.ts, with the source, for deploying); review and comment bodies are rendered on the server; editors, CID and pinning code, QR codes and the sandbox panel load on demand; KaTeX’s stylesheet only where formulas render: first-load JS shared by every page from 416K to 304K gzip, the article page from 636K to 352K. The algod genesis check is cached (10 minutes, 30 s on failure, 2 s timeout); the article page fetches in one round; immutable content, directory listings, version diffs, the About page, the donate QR and the sitemap are cached in process; pages wait at most 300 ms for names; Caddy compresses (zstd) instead of Node; the bell loads the newest 50 notifications and then only increments; feed links do not prefetch; the sandbox’s clock loop runs only while testers are active (it made about 180 algod requests a minute day and night); the sandbox panel pings every 60 s and only while visible. Kubo: a DHT client (Routing.Type autoclient) announcing only its pins, with 20-60 connections: it served 171 peers’ DHT queries with a quarter of a CPU and about 20 GB a day for 8 pins of its own. The migration was tried on a copy of the production TestNet database and the whole stack rehearsed locally before the deployment.
- Spec refs: §15, §16 · Tests: projects/indexer/test/d160.test.ts, projects/web/test/d160-bundle.test.ts, projects/web/test/d160-server.test.ts, projects/web/test/d160-server-inbox.test.ts
D-161 Responsible use of Nodely’s free tier (owner decision)
Section titled “D-161 Responsible use of Nodely’s free tier (owner decision)”- Date: 2026-10-07 · Status: accepted
- Decision: ARIADNE reads TestNet and MainNet through Nodely’s free public API (60 requests a second and 10 at a time per IP, 6 million requests and 5 GB a month, failed requests counted; projects above a million a month are asked to mention Nodely). The owner asked for responsible use. Measured on a copy of the production TestNet database, the indexer made 48 requests a minute (about 69 000 a day, 2.1 million a month for one network): algod’s status and each new block in full, every 2 seconds. Indexer: on a public network (not devOnly) with an Algorand indexer, every poll now goes through that indexer alone (
ARIADNE_SYNC=indexer, the default there;algodkeeps the block reading of development chains and the sandbox): one health request for the round it has indexed and one search per filter (application calls, list notes) up to that round, never past it (a transaction of a later round would be lost once the watermark moved), never a whole block nor algod’s status (the subscriber gets the tip ascurrentRoundand has no rounds left for algod); polls every 20 s instead of 2 (ARIADNE_POLL_SECONDSstill wins). Measured again: about 8.5 requests a minute (about 12 000 a day, 370 000 a month per network), new activity visible within about 20 s. A failed poll waits longer each time (the interval doubled, at most 10 minutes, at least a minute after a 429 or a 403), and every request names ARIADNE in its User-Agent. Web: the status page’s report is shared for a minute and the page refreshes every minute (it was 10 s and 15 s: about 7 probes to Nodely each time); the genesis check of a public network is cached for 6 hours (10 minutes on development chains and the sandbox). Transactions prepared in the browser go from the reader’s own address and do not count against the server’s. The About page credits Nodely. Pera’s public API was considered: it describes assets on MainNet only and cannot replace an algod or an indexer, so it is not used. - Spec refs: §15, §16 · Tests: projects/indexer/test/d161.test.ts, projects/web/test/d160-bundle.test.ts, projects/web/test/status.test.ts
D-162 Adversarial audit of the contract and its economics: every off-chain finding fixed (owner decision)
Section titled “D-162 Adversarial audit of the contract and its economics: every off-chain finding fixed (owner decision)”- Date: 2026-10-08 · Status: accepted
- Decision: three agents (Claude) attacked contract v3 and its incentives before MainNet: the contract code on LocalNet (no critical finding: the application account cannot be drained, no box of someone else can be written, a payment cannot serve two calls, the artefacts are reproducible byte for byte), the economics of reputation with an extended copy of the D-150 simulation, and the parity between the contract and the indexer (a differential fuzz of 17 443 random accepted calls found the reducer equal to the contract and to the Python oracle, and rebuild equal to live). They found that any article can be disputed in a day for about 1.6 ALGO (comment resolution mints reputation from nothing; one address with reputation 841 disputes alone), that a Peer-reviewed seal can be faked for about 0.64 ALGO with one ORCID iD behind two reviewers, that the jury’s pool was read after the seed was public, that the D-150 repeat-vote damping keyed on the submitter is bypassed by front submitters, that governance retraction is immediate and that one call could brick governance; the contract findings are fixed by contract v4 (D-163). The owner asked for everything to be fixed. Fixed now, off chain: ingestion (P-1, a regression of D-161): a public endpoint may answer the health request and a search from indexers at different rounds, so every search’s round is recorded and a poll never moves past the lowest (production was compared with a fresh full read: nothing had been lost); at start the indexer compares the deployed approval program with its ARC-56 and refuses another version; raw log bytes are kept and rebuild decodes from them; notification ids survive a rebuild. Jury: the eligible pool is frozen as of the flag that started the dispute (its hash is published), one ticket per ORCID iD, only confirmed authors are excluded, flaggers’ self-asserted employers are ignored, ORCID iDs and reputation must be 90 days old (reputation from at least 3 people outside the authors and flaggers), every deadline uses chain time, no draw for a dispute already resolved, no consensus never clears by itself (a second jury, then governance with public reasons), answers stay hidden on the site until the jury closes. Seal rule 5: one review per ORCID iD, expertise reputation given by at least 3 people other than the authors and earned 90 days before the review, a new conflict when an author supported the reviewer in the 3 years before, “changes requested” only from reviewers passing the same tests. Citations: a CID or DOI belongs to its first (or verified) article and duplicates are shown; Thread Score version 4 counts a citation once per citing submitter, cited article and year, and only from articles with a counted review or a vote of reputation 100. Interface: Google Scholar journal tags only from selections the submitter accepted (new list-note operation “selection”), amendments by non-authors shown as third-party notes, the submitter’s DOI with conflicts shown, inert co-authors never shown as people, claims under the daily cap, reputation by source, notifications from new accounts folded into a daily digest and at most 20 per sender and recipient a day, a pin quota of 200 MB per address per 30 days. The sandbox indexer runs with ARIADNE_AGE_DAYS=0 so its tools need no 90-day clock jumps. Schema 17, rehearsed on a copy of the production TestNet database.
- Spec refs: §15, §16, §18 · Tests: projects/indexer/test/d161.test.ts, d162-jury.test.ts, d162-seal.test.ts, d162-citations.test.ts, d162-ingest.test.ts, d162-followup.test.ts, projects/web/test/d162-web.test.ts
D-163 Contract v4, the MainNet candidate: bonds for flags, damping of repeated grants, two-step governance (owner decision)
Section titled “D-163 Contract v4, the MainNet candidate: bonds for flags, damping of repeated grants, two-step governance (owner decision)”- Date: 2026-10-08 · Status: accepted
- Decision (the dispute rules below were amended the same day, before any deployment: see the next bullet; the threshold stays at 30, the bond grows, expiry pays nobody): the contract findings of the adversarial audit (D-162) are fixed by a new contract, which replaces the D-150 rules where they differ. Disputes: a flag weighs at most 10 (1 + isqrt(reputation), capped), so a dispute needs at least three people; the flagger’s reputation box in the field must be 30 days old; every flag deposits a bond of 1 ALGO besides its storage, refunded when the dispute ends in a retraction or when the round lapses, paid to the submitter when the dispute is cleared or expires (settle_flag, which anyone may call; the flag’s storage deposit always goes back to the flagger, or with the bond when the flagger closed the account); a round that does not become a dispute within 30 days of its first flag lapses; the threshold doubles after each clearance for 365 days (30, 60, 120, 240, 480; the count of clearances is kept, so a later clearance doubles from it); a dispute expires after 60 days (expire_dispute, by anyone; it counts as a clearance); governance clears at once but retracts only 14 days after the dispute began, with the CID of its published reasons in the event; the submitter may retract a disputed article; votes, claims, confirmations of co-authorship, review and comment votes and comment resolutions continue while disputed (a dispute is a label and a jury case); the bond flows keep invariant 22 restated (spendable balance never below the bonds held, each bond paid out once). This replaces D-150’s “flags count less after a cleared dispute” by a direct cost. Damping (replaces D-150’s per-pair rule, which front submitters bypassed): one counter per (giver, receiver), key “d” + sha256(giver || receiver), counting every grant; the n-th grant is the weight divided by n (a weight-1 voter’s second vote for the same person is worth 0, by design); an article vote is damped by the largest count over all its authors (the voter lists the submitter and every declared co-author, and every author’s counter moves); comment resolution grants 5 // n and only when the resolver has reputation 10 in the field. Governance changes in two steps (propose_governance, accept_governance signed by the new address; the application’s own address is refused); set_governance is gone. Other: amendments only by an accepted author of the parent; DOIs only by the submitter; Followed.on canonical; reputation boxes record the day they were created. Events have schema_version 2 and a gap-free global event_seq; DisputeResolved names who resolved and the reasons CID; Voted carries the raw weight and n; Flagged the bond and the threshold; new FlagSettled and GovernanceProposed. Program 7 252 of 8 192 bytes; the largest single call costs 590 opcodes; measured costs in projects/contracts/V4_REPORT.md (a flag deposits 29 700 microAlgo plus the bond, 7 300 more when it opens the dispute). The audit’s attacks were ported as LocalNet tests and now fail or cost what this design says. TestNet restarts with a v4 application, as at D-110; the independent security review and the MainNet conditions of D-108 still apply.
- Amended the same day, before any deployment: the economics simulation re-run on the exact v4 code (8 seeds; results in the audit’s economics folder) confirmed that v4 closes the cheap attacks (a first dispute costs about 7.6 ALGO and 30 days of preparation instead of 1.6 ALGO in a day; nobody can dispute alone; the ring with front submitters falls from 1.89 to 0.012 times the best honest reputation; founders’ share of vote weight 10%, weight-to-talent correlation 0.75, the best of the rules compared) but found a new flaw: an author’s own accounts could dispute his article, take the bonds back when it was cleared or expired, and double the threshold each time (16 times for about 0.25 ALGO). So the threshold stays at 30 and the bond grows instead (1 ALGO << clearances, at most 16 ALGO, for 365 days after the last clearance); only a governance clearance counts; at settlement a cleared dispute pays half the bond to the submitter and the application keeps the other half, an expired dispute’s bonds are all kept (there is no withdrawal path: they are burned), a retraction or a lapse refunds them; FlagSettled carries the amount retained, and invariant 22 counts retained amounts. Off chain: the notes of rounds that never became disputes are not shown; for the seal and the jury a granter counts as a person only with a verified ORCID iD or rule-5 expertise, and grants between reciprocal pairs are ignored; Thread Score’s vouching vote must come from a rule-5 expert. Coordinated-voting detection moves before MainNet (a ring of 50 accounts voting each other’s reviews useful still reaches the best honest reputation for about 54 ALGO per account and year). Program 7 307 bytes. A differential fuzz of the amended contract (8 seeds, 56 707 accepted calls, every bond level from 1 to 16 ALGO) found the contract, the Python oracle and the indexer in exact agreement, and the application balance equal to the bonds held plus what was retained. Left as is and noted for the independent review: a declared co-author may flag the article before confirming co-authorship.
- Spec refs: §5, §6, §7.3, §7.4, §8, §9, §10, §11, §12, §13, §17, §18 · Tests: projects/contracts/tests/unit/test_v4_disputes.py, test_v4_damping.py, test_create_governance.py, tests/localnet/test_96_v4.py, test_98_audit_pocs.py, test_82_invariant22_m2.py
D-164 Privacy and legal audit; the legal front end and the off-chain fixes (owner decision)
Section titled “D-164 Privacy and legal audit; the legal front end and the off-chain fixes (owner decision)”- Date: 2026-10-08 · Status: accepted (the legal texts are drafts for the owner’s lawyer)
- Decision: three agents (Claude, not lawyers) audited ARIADNE for data protection (GDPR, LOPDGDD, ePrivacy/LSSI cookies), platform law (DSA, LSSI-CE, the TCO Regulation on terrorist content, copyright, defamation, research data) and crypto, consumer, licence, trademark and accessibility law, with primary sources checked as of this date. Main findings: no legal notice, privacy policy, terms, contact or notice channel existed (DSA arts. 11, 12, 14, 16; LSSI art. 10; GDPR arts. 13-14); the published policy of acting only on binding requests would lose the hosting safe harbour (DSA arts. 6, 16(3)); there was no emergency path for terrorist content (one hour, TCO art. 3) or child sexual abuse material, and the transparency page would have listed their record numbers; the indexer copied ORCID e-mails; withholding reached only articles, reviews and comments and kept their texts; texts promised things the service does not do (no e-mails collected, a warning before every signature, an access log). The owner chose to fix everything that needs no contract change now (block A); the on-chain design (ORCID iDs and free-text notes on chain in clear) goes to a contract v5 before MainNet (block B); the owner’s own tasks (an entity, the operator’s identity in the legal notice, the lawyer’s review, tax advice on donations, the trademark, the AGPL question, the impact assessment) are block C. Done: a legal notice and contact page (English and Spanish), a privacy policy, terms of use (English and Spanish) accepted before the first signature, a DSA notice form (anonymous for CSAM) and a rights page where an address is proved by a signed message, an owner’s view with phone alerts, security.txt, a licences page with the notices of the code sent to browsers; the operator’s identity is read from the server’s environment and never stored in the repository. Reviews and comments are published under CC BY 4.0, articles must choose a licence, minimum age 16, Spanish law with consumers’ own protections. Withholding covers every kind of text and person and earlier applications, with grounds (order, notice, terms, data protection, copyright, csam, terrorist, other), statements of reasons and redress, purges stored texts, is reloaded from a file the owner can edit on the server, and the transparency page shows sensitive cases only as monthly counts; Kubo applies the IPFS Bad Bits list; terrorist content is preserved encrypted for six months (TCO art. 6), child sexual abuse material never; an emergency playbook works from a phone. ORCID e-mails are no longer read and stored ones were deleted; ORCID data is kept only while an iD is verified, and a declared iD is shown as unauthenticated (ORCID’s guidelines). A person may object to profiling (a public list note): their Thread Score and reputation figures are hidden and they are excluded from juries. Logs keep no full IP address and no User-Agent; push subscriptions without delivery for a year and sandbox reports after six months are deleted; the wallet modals no longer load Google Fonts; outside services get an identifying User-Agent and their rate limits are respected. Wording follows consumer law (non-refundable storage deposits, no tradable token, gifts to the operator, a commission on NFD sales). Late research found an EU trade mark “Ariadne” registered on 19 June 2026 by Ariadne GmbH for software and SaaS (classes 9, 35, 41, 42): renaming before MainNet is being considered (FILUM screened as the best candidate; a trademark attorney must clear it).
- Spec refs: §14, §15, §16, §18 · Tests: projects/web/test/legal-.test.ts, d164-web.test.ts, projects/indexer/test/d164-.test.ts
D-165 The project becomes Cabdell, at cabdell.press (owner decision)
Section titled “D-165 The project becomes Cabdell, at cabdell.press (owner decision)”- Date: 2026-10-11 · Status: accepted (the name still needs a trademark attorney’s opinion before MainNet)
- Context: the EU mark “Ariadne” (Ariadne GmbH, classes 9/35/41/42) conflicts with the project’s name (D-164). After screening many candidates the owner chose Cabdell (Valencian and Catalan for a ball of yarn: Ariadne’s ball of thread) and the brand identity “One thread, wound and followed”, and bought
cabdell.press(DNScabdell.press,www.,api.,ipfs.to the server, 2026-10-11). Full screening (agent, 2026-10-11; summary in the session’s notes): no CABDELL mark anywhere in TMview; the risk is DELL (Dell Inc.): LEXDELL (General Court T-641/14) and TaiDell (OD B 3 118 304) were refused, so registration in class 9 is medium-high, 42 medium, 41 low-medium; CAPDELL (EUTM 006621296, furniture, class 35) sounds the same; the journal “Cabdells” (Oliva, since 1999) and parkedcabdell.es/cabdell.catexist. Mitigation followed in the design: one word, lower case in the logo, the thread running under the whole word, no blue, no circle around the word, never “CabDell” or “CABDELL”. - Name in text: Cabdell (capital C only), the site cabdell.press; never all capitals and never split. This replaces D-096’s “ARIADNE in capitals”. The former name is said once where the name is explained (About, the docs’ introduction, the legal notice and the footer: “Cabdell was called ARIADNE until October 2026”, with the non-affiliation note).
src/lib/brand.tsholds the name, the palette and the mark’s geometry. - Identity (design canvas “Cabdell — brand identity”): the symbol is a spiral of thread drawn as one stroke (five half-turns; three below 40 px) whose loose end, the only orange, leaves to the right and in the main lockup underlines the name. Colours of the Valencian horta: Calç
#FAFAF7(ground), Nit#121A17(text), Horta#17463A(brand, buttons), Taronja#F07B1F(the thread, lines and fills only), Taronja fosc#B4500C(orange text), Taronja clar#FFA25C(thread on dark); the dark theme on green-black#0F1A16with a mint accent#8FD1B3; papyrus (D-159) keeps its palette and takes the mark’s colours. Type: Bricolage Grotesque (interface and headings), Newsreader (articles, reviews and comments), DM Mono (addresses, CIDs, figures), served from the site through the@fontsourcepackages (OFL-1.1, listed in /licenses by the notices script), no font service. The logo’s letters are outlines (scripts/brand_outlines.py, fontTools and HarfBuzz from the served font files, at the display optical size) so the lockup, the icons, the link preview and the docs’ logo look the same everywhere;scripts/pwa-icons.mjsandscripts/og-image.mjswrite the favicon, the app icons, the docs’ logo and the 1200 x 630 card. - Domain:
cabdell.pressis the app (APP_HOST),api.cabdell.pressandipfs.cabdell.pressthe indexer and the gateway;ariadne.press,www.ariadne.press,www.cabdell.pressand the sslip.io name redirect permanently (LEGACY_APP_HOST), andapi.ariadne.press/ipfs.ariadne.presskeep answering next to the new names. The manifest gainslegacyAppUrls: ["https://ariadne.press"]for TestNet and MainNet: the indexer accepts profile links and DOI records that point at the old origin (ORCID iDs already verified stay verified), and both the indexer and the web count links to articles at the old origin as links to this site (citations, link previews, the pre-publication checks). New ORCID instructions, Zenodo drafts, citations and the User-Agent use cabdell.press. Browser storage belongs to an origin, so a reader’s theme, pinning keys and wallet session must be set again at cabdell.press; the owner addshttps://cabdell.press/api/zenodo/callbackto the Zenodo sandbox application. - Unchanged until the next contract (v5): the contract’s name and events, the per-article tokens (
Ariadne Article,ARIA), theARIADNE_*settings, package and image names, browser storage keys, the file format of preserved notices (ARIADNE-PRESERVED-1), the donation NFDariadne.algoand the Zenodo communityariadne-algo.spec/ARIADNE_SPEC.mdkeeps its name and text with a note that ARIADNE reads as Cabdell; this log keeps its history. - Spec refs: §16 · Tests: projects/web/test/branding.test.ts (lockup, name, manifest and former origin), docs.test.ts, legal-pages.test.ts; projects/indexer/test/verify.test.ts (former origin)