7.8 KiB
6. Addresses and anchors
6.1 Addresses
An address names a node, optionally pinned to a hash and narrowed to a span:
address = [store "/"] key "#" node ["@" pin] ["[" start ":" end "]"]
store = lowalnum *(lowalnum / "." / "_" / "-")
key = workkey *("." lowalnum *(lowalnum / "-")) ; a document key (2.4)
workkey = LOWER *lowalnum "-" 4*51BASE32
node = lowalnum *(lowalnum / "." / "_" / "-") ; a node ID or an alias
pin = 6HEXLOWER ; the first six digits of a node_hash
start = "0" / (%x31-39 *DIGIT)
end = "0" / (%x31-39 *DIGIT)
lowalnum = LOWER / DIGIT
LOWER = %x61-7A
BASE32 = DIGIT / %x61-68 / %x6A-6B / %x6D-6E / %x70-74 / %x76-7A ; Crockford, lowercase
start is not greater than end, and both are code-point offsets into the plain projection of the node in the pinned state.
| Example | Names |
|---|---|
cornell1995second-saqk#k3m9q2a |
A sentence, whatever its current text. |
cornell1995second-saqk#k3m9q2a@4f1a0c |
That sentence as it read when its node_hash began 4f1a0c. |
cornell1995second-saqk#k3m9q2a@4f1a0c[12:57] |
Code points 12 to 57 of its plain projection in that state. |
smithlab/cornell1995second#k3m9q2a |
The same sentence in the smithlab store. |
On the web the node ID is the fragment: …/smithlab/cornell1995second#k3m9q2a lands on the sentence.
addresses.jsonl gives valid and invalid addresses and how each parses.
6.2 The anchor
An anchor, scienting-anchor/1, is the stored form of a pinned address, with enough context to survive change.
It is what extracts, annotations, dataset values and draft citations use.
| Property | Required | Holds |
|---|---|---|
format |
Yes | scienting-anchor/1. |
store |
No | The store. |
doc |
Yes | The document key. |
node |
Yes | The node ID, or a legacy alias. |
kind |
No, RECOMMENDED | The node's kind when the anchor was made. |
own_hash, node_hash |
Yes | The node's values when the anchor was made. |
span |
No | [start, end] in code points; present for span anchors. |
quote |
With a span, or on a leaf | {exact, prefix?, suffix?}, or {exact_hash, length, prefix?, suffix?} for quotations longer than 200 code points. |
context |
On a cell | {table_label, row_path, column_path}: the table's label and the normalized plain projections of the row's and column's header cells, outermost first. |
leaves |
On a container without content | [[id, match_hash], …]: every leaf below the node, in document order. |
rev |
Yes | The document's revision when the anchor was made. |
keyed |
No | The hashing scheme, when the anchor leaves the team (6.3). |
exact is the substring of the stored plain projection at the span, or the whole projection for a leaf anchor.
prefix and suffix are up to 32 code points before and after it, omitted when empty.
exact_hash is SHA-256 of the normalized quotation, and length its length in code points.
Revisions are counted, not timed, because anchors are made on different machines and clocks disagree.
6.3 Keyed anchors
When an anchor leaves the team, in a public layer or on a work whose text is withheld, it carries keyed, and:
own_hashandnode_hashare computed under the key;quotebecomes{mac, length, prefix_mac?, suffix_mac?}, each MAC over the normalized text; andcontextbecomes{table_label_mac, row_path_mac, column_path_mac}in the same way.
Such an anchor can be verified, and its span shown, only over a copy of the paper the reader holds.
6.4 Four kinds of anchor
| Kind | Points at | Judged by |
|---|---|---|
| Span | Part of a leaf's text: the anchor has a span. |
The quote, then node_hash. |
| Leaf | A whole sentence, cell, reference, authors, keywords, equation, equation-line, code or algorithm. |
node_hash. |
| Container with content | A section, figure, panel, table, theorem or footnote. |
own_hash, with node_hash reported. |
| Container without content | Any other node. | Its ID and its leaves. |
6.5 Resolution
To resolve an anchor against a document's current state (its nodes, its ID table and its corrections):
Follow the ID.
If the anchor's node is live, use it.
Otherwise follow the ID table: an alias leads to its node, a retirement to its successor, repeatedly, until a live node is reached, a deletion ends the chain, or an ID is unknown or repeats.
The anchor is retired when the chain passed a retirement; an alias alone does not retire it.
The kind is the anchor's kind, else the live node's, else the retired entry's node_kind.
Find the quote.
The quote's hits in a text are the positions where exact occurs, or, for a hashed quote, where a window of length code points has that hash.
When there is more than one hit and the anchor has a prefix or suffix, keep the hits preceded by the prefix and followed by the suffix.
Judge, by kind:
| Kind | Current | Changed below | Moved | Reattached | Otherwise |
|---|---|---|---|---|---|
| Span or leaf | Not retired, and node_hash equal. |
Never. | Retired, and the successor's text has exactly one hit (a span) or at least one (a leaf); a span takes the new position. | Not retired, changed, and exactly one hit, with the new position; or a changed node whose only changes since rev were relabel corrections, and whose own content with the earliest of those corrections' from label has the anchored own_hash. |
Stale. |
| Container with content | Not retired, and own_hash and node_hash equal. |
Not retired, own_hash equal, node_hash not. |
Retired, and the successor's own_hash equals the anchored one. |
Not retired, and only relabel corrections changed it, judged as for leaves. |
Stale. |
| Container without content | Live and node_hash equal. |
Live and node_hash not equal. |
Retired, and the successor's leaves hold at least half of the anchored leaves, each matched by ID or else by match_hash. |
Never. | Stale. |
The cell rule.
A cell anchor whose ID led to no live node (a deletion, or an unknown ID), or whose successor fails the leaf rule, is reattached when exactly one cell in the document has the anchored context and exactly one hit of the quote.
This finds a value again even in a table with multi-level headers or row labels repeated across parameter sets.
The result is {result, node, node_hash?, span?, drift?, via?, reason?}: the outcome, the live node and its node_hash, the new span, the drift of a changed-below result, the IDs followed, and a reason for stale and relabel results.
A moved or reattached anchor is re-pinned to the live node, and the move is recorded.
Checks are stricter than readers: an extract is a span or leaf anchor and never accepts changed-below, because a check is about exact words.
resolve.jsonl resolves seventeen anchors made at revision 0 against fixtures/packages/hale2026harmonic-damb at revision 12, with every outcome.
6.6 Drift
changed-below must not last forever.
For a container c and an anchor made at revision r:
- The corrections that count are those with
indexgreater than r whosepathorto_pathcontains c and whosetouchedlist is not empty. countis their number, and T the union of theirtouchedlists.- L is the set of leaves below c (including c itself if it is one) in the current state.
shareis |T| divided by |L ∪ T|, written as a decimal with four places, rounded half up.levelisnonewhencountis 0,highwhenshareis greater than the threshold, andlowotherwise.
The threshold is a quarter by default; a project may set another (20).
A reader shows a high drift as needing review.
A change to a section's heading changes its own_hash, so a rename is never mistaken for drift.