Clay is a local-first post feed organized into subs.
A sub is a Lace Group scope such as clay/rust, not a
central server or owner. Participants keep their own marked records,
aggregators independently admit participants and publish bounded
Rankings and Threads, and readers choose which aggregators to trust in
each sub.
There is no central Clay account authority or mandatory relay. One aggregator key may serve several subs through one authenticated WSS route.
Clay is an incubator application. It has no compatibility or migration promise; use fresh stores after record or policy changes.
participant follows subs, reads, requests Membership, submits, votes, comments
aggregator owns per-sub Membership, accepted records, Rankings, and Threads
Authority stays explicit:
A sub slug grants no authority. Membership in one sub cannot unlock writes or a display name in another. Marks prove key control, not human identity, factual truth, authorization outside the active policy, or honest ranking.
A sub is a lowercase ASCII slug of at most 64 bytes:
[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?
Exchanged Clay records use:
Group: clay/<sub>
Clay uses Lace-300 fragment state. Canonical addresses are:
lace://u//clay/http//index.html{#:rust}
lace://u//clay/http//index.html{sort:latest,#:rust}
lace://u//clay/http//index.html{#:rust/S.<submission>.H3}
lace://u//clay/http//index.html{#:rust/S.<submission>.H3/S.<comment>.H3}
The Lace HTTP compatibility bridge projects them to:
.../index.html#rust
.../index.html?sort=latest#rust
.../index.html#rust/S.<submission>.H3
.../index.html#rust/S.<submission>.H3/S.<comment>.H3
An aggregator invitation includes one sub and one route hint:
lace://u//clay/http//index.html{aggregator:V.<aggregator>.H3,via:"wss:aggregator.example/interlace",#:rust}
via is routing only. Parsing an invitation grants no
authority. The aggregator key constrains accepted Membership, Ranking,
and Thread Marks; successful live WSS proof establishes that the
connected endpoint controls the matching By-secret value.
popular is the default ordering and is omitted from
canonical addresses; sort:latest selects latest.
Feed-to-discussion navigation preserves the ordering so Back returns to
the same feed. A bare Clay address immediately canonicalizes to
{#:clay} (or #clay through the HTTP bridge),
and Home returns to that default popular feed. Clay has no cross-sub
feed or global sub registry.
One HTTP deployment configures one exact directory invitation beside
its Lace-320 interface descriptor in
browser-address.mjs:
lace://u//clay/http//index.html{aggregator:V.<directory>.H3,via:"wss:directory.example/interlace",#:clay}
This is host-owned local configuration, not address-state input and
not a global Clay authority. On a fresh Follow history, Clay persists
one ordinary weight-1 Follow for this directory and starts
its ordinary authenticated route. Existing Follow history prevents
automatic mutation: a positive-weight Follow remains unchanged, a
weight-0 community stays unfollowed, another saved route
requires confirmation, and unrelated history leaves an explicit
Follow directory action. Invalid configuration fails
startup visibly before Follow state is read. A projected HTTP invitation
authorizes automatic bootstrap only when its reconstructed canonical
Lace address exactly equals the configured value; Clay consumes that
projection with history replacement. Other invitation addresses and the
manual form require an explicit reader action.
Build the static deployment with:
npm --prefix incubator/clay run build
An ordinary Lace-320 compatibility host maps
(u, clay/http) to incubator/clay/dist/static,
serves the configured interface key, and may redirect its root to
/lace//u//clay/http//index.html. The base bundle entry is
//u//clay//index.html. Edit the separate exported
directoryInvitation before building for that deployment.
Keep /interlace routed to the aggregator service’s WSS
proxy. The compatibility host serves no Clay records; the aggregator
service serves selected records only through ILTP.
Clay has fourteen exact App families, counting popular and latest Rankings separately. Unknown fields and noncanonical bodies are invalid. Current state chooses greatest TAI, then bytewise-greatest record hash. An invalid winning current record has no historical fallback.
Submission, Vote, popular Ranking, latest Ranking, MembershipRequest,
Membership, Comment, Thread, ControlCommand, ControlOutcome, and
Moderation are marked Seal records under
Group: clay/<sub>. Follow, OperatorAuthorization, and
ControlProfile use Group: clay. Follow is reader-local;
operator control records follow the private exchange rules below.
App: submission
Name: post
Data: {"title":"...","target":"https://.../"|"lace://..."|null,"text":"..."|null,"aggregatorName":"..."|null}
The participant marks the immutable record. Title is required; target
and text are independently optional and may both be absent. A target is
either a normalized absolute HTTP(S) URL or a canonical absolute
Lace-300 browser address. HTTP fragments are removed; Lace JSONqa,
including #, is preserved. Relative references are rejected
because copied Submissions have no stable resolution base. A Submission
may target the exact canonical address of another Clay post; that is an
ordinary linked post with its own Submission identity, votes, and
discussion in the destination sub. Present text is nonempty plain NFC
UTF-8 with LF line endings and the same supported control characters as
Comment text. Titles are at most 240 UTF-8 bytes, targets 2,048 bytes,
text 8 KiB, and the complete canonical JSON body 20 KiB.
aggregatorName is canonically null for an
ordinary post. Clay has no Submission edit family: changed content
creates a new Submission hash.
An aggregator advertisement is this same Submission under
Group: clay/clay. It has a non-null normalized
aggregatorName of at most 80 UTF-8 bytes, its target is an
exact canonical feed-view Clay invitation with no Submission or Comment
hash, and its exact checked By equals the invitation’s
aggregator. The title remains the post headline, such as
Join my #rust community; aggregatorName is the
suggested sidebar/settings name, such as Rust Community;
optional text is the blurb. A non-null aggregatorName on
any other Submission shape is invalid. No Profile, Introduction,
Local-Name, Alias, or separate discovery family exists.
App: vote
Name: vote
Submission: S.<Submission>.H3
Vote: Up|Down|None
Data: empty
The participant marks the record. Current state is keyed by sub,
voter, and Submission hash. None is the tombstone. A
participant may vote from a compact Ranking without storing the original
Submission; an aggregator accepts that Vote only after independently
admitting the exact target in the same sub.
App: ranking/popular | ranking/latest
Name: period/<aligned daily period end>
Period-End: <the same aligned daily period end>
Window-Start: <the canonical seven-day window start>
Entry+Link: entry S.<Submission>.H3
[repeated in body order]
[Previous-Ranking+Link: previous S.<Ranking>.H3]
Data: {"entries":[...]}
The aggregator publishes one bounded popular Ranking and one bounded
latest Ranking for each sub and daily period. Each contains at most 100
entries and a 512 KiB body. Both carry the Submission hash, exact
postAddress, publisher, Submission TAI, aggregator-endorsed
member name, title, nullable target and aggregatorName, and
hasText. Popular additionally carries canonical
score, up, and down; latest
contains none of those vote claims and orders by descending Submission
TAI then ascending hash.
Current state is keyed by sub, aggregator, ordering App, and period.
Period-End and Window-Start let policy bind
the complete period without parsing Name; they exactly
match the canonical aligned period and its derived seven-day window. A
current candidate has that exact envelope and a body of at most 512 KiB.
Envelope-invalid records are not candidates. Among candidates, greatest
Ranking TAI then hash wins; a body-invalid winner blocks fallback.
Vote-only changes may revise popular while reusing latest. Normal
synchronization requires a valid popular/latest pair at the same
greatest publication period.
The optional predecessor points to the final winner from the greatest earlier published period for the same sub, aggregator, and ordering. Same-period revisions preserve it and never become history pages; publication gaps may be skipped. A linked predecessor must validate with the same Group, By, and App and a strictly earlier period. A malformed or missing link stops that chain rather than substituting an unlinked local Ranking. Reachable history is intentionally public to policy evaluation, while exact bounded retrieval transfers one linked record into the reader Lace.
postAddress identifies where a reader may retrieve the
exact original Submission. Ranking entries remain self-contained display
claims and never carry Submission text. Normal feed synchronization
transfers current snapshots, not original Submissions, Votes, or
complete history.
Group: clay
App: follow
Name: <aggregator record-By value>
Aggregator-Index: <positive reader-local integer>
Sub: <sub>
[repeated in ascending sub order]
Weight: 0..100
[one Weight at the matching occurrence index for each Sub]
Data: <canonical Clay route address containing exactly aggregator and via>
One current Follow owns the reader’s complete relationship with one
aggregator: one WSS route, one stable reader-local aggregator index, and
independent trust weights for its subs. Current state is keyed by reader
and aggregator. The first valid Follow assigns the smallest positive
index absent from every valid Follow in that reader’s complete history.
The index is never reused and every later version preserves it across
route replacement, weight changes, unfollowing, explicit re-following,
and directory recovery. One index identifies the aggregator across all
communities. The index is local UI metadata: Follow records never
synchronize, &N never enters addresses or wire data,
and the index does not affect policy, ranking, or tie-breaking.
Weight 0 is the tombstone for an unfollowed community
while retaining its route, index, and history. A positive weight means
followed. Re-following is an explicit Follow with a newly chosen
positive weight; Clay does not restore a weight from history. The active
local projection permits at most 16 positive-weight Sub/Weight pairs
across all aggregators. One Follow retains at most 256 pairs; when
adding another pair at that bound, Clay removes the bytewise-smallest
unfollowed pair and preserves every followed pair.
Opening an invitation updates one pair while preserving the others. Replacing a saved route requires explicit confirmation and preserves the current Follow weight. Follow records never synchronize.
App: membership-request
Name: <aggregator record-By value>
Proposed-Name: <display name>
Data: <optional private message>
The participant marks the record. Current state is keyed by sub, aggregator, and participant. Display names are nonempty normalized text of at most 80 UTF-8 bytes; messages are at most 512 bytes.
MembershipRequests are policy-hidden plaintext records. They travel only over authenticated WSS and never enter ordinary reader exposure or transfer paths.
App: membership
Name: <participant record-By value>
Active: true|false
Display-Name: <sub-scoped endorsed name>
Data: empty
The aggregator marks the record. Current state is keyed by sub, aggregator, and participant. Active Membership permits subsequent Submission, Vote, and Comment selection only in the same sub. Inactive Membership blocks later selection and suppresses that participant’s content from future Rankings and Threads in that sub; immutable stored bytes remain.
A request newer than an inactive Membership is reviewable. A Membership decision advances beyond prior Membership and to at least the request TAI. Display names are labels, not unique usernames; record-By values remain account identity.
App: comment
Name: comment
Parent+Link: reply S.<Submission-or-Comment>.H3
Submission: S.<root-Submission>.H3
Data: <comment text>
The participant marks the immutable record. Comment text is nonempty
NFC UTF-8 with LF line endings and is at most 8 KiB. A top-level Comment
links its root Submission; a reply links its immediate parent Comment.
Submission remains a plain projectable root hash for
request selection. The record hash is Comment identity; Clay adds no
mutable comment name, depth, path, author field, edit version, or delete
flag.
An aggregator may admit an orphan while evidence arrives, but it publishes the Comment in a Thread only after validating same-sub root and complete acyclic parent closure.
App: thread
Name: submission/S.<root-Submission>.H3
Comment+Link: comment S.<Comment>.H3
[repeated in canonical parent-before-child order]
Submission+Link: root S.<root-Submission>.H3
Data: {"entries":[{"comment":"S.<Comment>.H3","memberName":"..."},...]}
The aggregator marks the bounded manifest. A Thread contains at most
100 Comments and a 64 KiB body. Its body has exactly one entry per
Comment+Link in the same order. The participant-marked
Comment remains content authority; the Thread entry supplies only that
aggregator’s sub-scoped display-name claim.
A usable Thread has one valid same-sub root, valid same-root Comments, no self-parenting, cycles, missing parents, or cross-root edges, and parent records before children. Siblings use descending Comment TAI then ascending hash to produce canonical parent-before-child preorder. Entries and links are unique.
Current state is keyed by sub, aggregator, and root Submission. When Membership or selection removes every Comment, the aggregator publishes an empty current Thread rather than leaving stale selected content. Unchanged logical manifests reuse the existing record.
Opening a discussion converges the current Thread and then its
checked Submission+Link and Comment+Link
records by hash in later fixed-point rounds. Feed synchronization
therefore stays compact while an explicitly opened thread retrieves the
original participant records needed to display it.
For daily Ranking period end E, input is exactly:
[E - 604800 seconds, E)
Aggregator advertisements are ordinary #clay Submissions
in this same window. A later directory Ranking omits an advertisement
after seven days unless its aggregator reposts it. Reposting creates a
new immutable Submission, discussion, and vote history. An offline
reader may continue displaying the newest cached older Ranking until
another arrives; Clay adds no hidden local freshness rule.
Per sub, one participant contributes at most 200 Submissions and
1,000 distinct Vote targets in the window. The greatest Vote TAI/hash
for each voter and target wins; None removes its count.
Both publisher and voter require current active Membership from the
producing aggregator.
Ranking score remains:
Up + Down <= 1000000
age = whole seconds from Submission TAI to E
signal = max(Up + 1 - Down, 0)
Score = floor(signal * 1000000000000 / (age + 7200)^2)
Ranking order is descending Score then ascending Submission hash.
A reader blends Rankings from followed aggregators for the active sub. Each entry score is multiplied by that Follow’s positive sub Weight and summed by exact Submission hash. The browser may temporarily hide any followed aggregator in its in-memory presentation filter. Hidden aggregator reports are excluded entirely from scores, post inclusion, displayed member-name provenance, Threads, and Comments, but remain synchronized: hiding changes no Follow, route, policy, or network interest and resets on reload. Display claims come from greatest Weight then ascending aggregator key. Link-only exact-target duplicates retain greatest final score then ascending Submission hash. Text-bearing and target-free posts remain distinct by exact Submission hash. The UI may sum each contributing Ranking’s reported Up and Down values, but labels those raw sums as non-deduplicated because the same voter may appear at several aggregators.
For discussions, each aggregator publishes its own selected Thread. Clay deduplicates exact participant Comment hashes. A Comment’s endorsement weight is the sum of sub Weights for valid Threads containing it. The rendered immutable reply tree sorts siblings by descending endorsement weight, ascending Comment TAI, then ascending Comment hash. Display names use greatest contributing Weight then ascending aggregator key and retain source provenance. Comment voting is deferred; the browser exposes no Comment vote controls or vote state.
A Thread is explicitly a selected set, not a completeness claim. The UI does not infer that omitted comments do not exist.
One route actor owns one open authenticated interlace per aggregator,
regardless of how many subs the Follow contains. Its policy uses one
linked static relation of positive-weight clay/<sub>
Groups. Follow weights remain local projection inputs and do not enter
exchange policy.
Adding or removing a sub, learning a write period, or opening or
closing one discussion prepares one complete policy and stages
replacePolicy() on that actor. The active discussion adds
one exact Thread interest; it does not open a second long-lived
connection. Independent route failures do not stop healthy
aggregators.
The participant policy selects per active sub:
The aggregator endpoint is configured with a finite exact sub set.
Its operand is stable across daily rollover: checked Ranking envelopes
dynamically select the greatest complete popular/latest pair and its
Window-Start/Period-End window. A partial
newer pair leaves the previous complete period active. Storing the
second head advances the ordinary open interlace through the
current-StorePosition wake; there is no endpoint-period policy
replacement.
The endpoint accepts hidden MembershipRequests, accepts participant content only under same-sub active Membership, publishes public Membership/Ranking/Thread state, exposes predecessor history reachable from each current Ranking head, and transfers only records selected by both operands, including exact current- or history-linked retrieval. Advertisement dependencies remain request-only; whole-record exposure remains the peer-policy inspection boundary.
Application limits are:
| Scope | Limit |
|---|---|
| followed Sub/Weight pairs per reader | 16 |
| retained Sub/Weight pairs per aggregator Follow | 256 |
| configured subs per aggregator | 16 |
| configured operators per aggregator | 16 |
| reviewable requests exposed per sub | 256, oldest first |
| current Memberships exposed per sub | 1,024 |
| pending operator commands per operator/sub round | 256, oldest first |
| operator-visible Submissions per sub | 1,000 |
| operator-visible Comments per sub | 1,000 |
| Submissions per participant/sub/seven-day window | 200 |
| Vote targets per participant/sub/seven-day window | 1,000 |
| Comments per participant/sub/seven-day window | 200 |
| Ranking entries | 100 |
| Thread Comments | 100 |
| Submission text | 8 KiB |
| Submission body | 20 KiB |
| Comment body | 8 KiB |
| Ranking body | 512 KiB |
| Thread body | 64 KiB |
Lace protocol and operation limits apply independently.
The first local Submit, Vote, or Comment without active Membership in the current sub opens the Join dialog. It selects one followed aggregator for that sub, collects a proposed display name and optional message, saves the MembershipRequest locally, and resumes the original intent. Pending review does not block local writes.
Membership review is manual. web/aggregator.html owns
one separate persistent operator By-secret and one isolated persistent
operator Lace per aggregator. Private content copied from one aggregator
therefore cannot appear as another aggregator’s control state. The
service admits an operator command only while the live WSS session
proves that operator and a current private OperatorAuthorization is
active. Admission is final: removing an operator from configuration
prevents later command admission but does not cancel commands already
admitted.
A private aggregator-marked ControlProfile carries the exact
configured clay/<sub> Groups. One unchanged operator
policy receives that profile, then uses the next ordinary interlace
round to unlock bounded requests, memberships, content, Moderation, and
outcomes. There is no bootstrap policy replacement.
ControlCommand supports accepting or rejecting an exact reviewable
request, deactivating an exact active Membership, and removing or
restoring one exact Submission or Comment. A successful command produces
the aggregator-owned Membership or Moderation directly, with
Command+Link identifying the command. Conflict or rejection
produces a private ControlOutcome. There is no second Applied receipt
and no transactional multi-record assumption.
Moderation is default-allow. Remove excludes a
Submission from future Rankings and empties its future Thread, or
excludes a Comment and descendants whose selected parent closure is then
missing. Allow restores ordinary eligibility. Removal does
not delete immutable bytes or recall copies already received
elsewhere.
The browser has two durable view shapes in one document: a sub feed
and a discussion with optional focused Comment. Missing fragment state
canonicalizes to the popular #clay feed. Popular blends
score claims and exposes score/up/down reports; Latest orders by
greatest claimed Submission TAI and never fabricates vote claims. It
navigates through window.address and supports reload and
browser back/forward state. Feed and discussion right sidebars list the
followed aggregators for the current sub. Each has a temporary
show checkbox. Unchecking it keeps the active
relationship card visible but muted as hidden in this
view, reprojects the already synchronized local Rankings and
Threads, and performs no Follow, store, route, policy, interest, or peer
operation. This in-memory filter resets on reload. When the latest
locally checked self-authored #clay advertisement for an
aggregator’s saved route and current sub is available, its
aggregatorName and blurb label the aggregator; its title
remains the post headline. The host directory is labeled
Configured directory; another aggregator without a local
advertisement falls back to its shortened record-by value. Other subs
link back to #clay; there is no second suggestion list.
Directory bootstrap state, configured identity and route, connection
detail, temporary per-aggregator presentation checkbox, Follow weight
and explicit unfollow controls, and the manual invitation form live in
Aggregators settings. An unfollowed relationship is retained as history
with an explicit follow again action. The browser’s
local Lace store is clay-reader. Desktop keeps the
aggregator list inside the bounded content rail to the right of the
primary column. Narrow layouts preserve primary content first and stack
the list below it without horizontal overflow.
Opening a feed pins one usable paired head per visible aggregator. New heads do not reorder visible rows: Popular offers View updated ranking, while Latest offers View new stories when a visible identity was added and otherwise View updated latest. Activating the action resets the pinned projection at the top. Sub, sort, Follow, and temporary show/hide changes also create a fresh projection generation, so late historical retrieval cannot append into a replacement.
Load older stories follows each pinned ordering’s exact predecessor once, retrieves those records through bounded authenticated interlace into the reader Lace, blends one source-relative cohort, removes identities already shown, and appends without reordering existing rows. The end-of-list observer invokes that same manual path. A transient route failure leaves only that source cursor for retry; an invalid or missing predecessor visibly ends only that chain. An empty deduplicated cohort disables automatic loading until the explicit button is used again. These are historical bounded aggregator claims, not ranks beyond 100, a complete Submission log, or a globally comparable score chronology.
Submit, Vote, Comment, reply, Follow, and MembershipRequest controls update optimistically and persist to the local Lace store without waiting for network fixed point. A new Submission remains visibly marked as saved locally until an aggregator Ranking includes it. Selected upvotes are orange and selected downvotes blue. A feed row keeps blended score, aggregator count and reports, and the complete checked original Submission behind one information dialog. Its original-record retrieval first uses checked local records and may try other contributing aggregators; loading and retrieval failure remain inside that dialog.
Feed posts, discussion context, and Comments show a compact
submitted <relative age> by <chosen display name> on &N
byline whenever a Ranking or Thread supplies the display aggregator.
&N means reader-local aggregator N; optimistic/local
rows without that provenance omit it. Sidebar cards and information
reports use the same local index and provide accessible
local aggregator N text. An HTTP target opens externally, a
general Lace target is an ordinary canonical Lace anchor rewritten by
the HTTP bridge, and a target-free title opens discussion. A provisional
feed-view invitation in a #clay Ranking keeps title and
text actions on the discussion and exposes a separate
sub-specific Follow button. That button first retrieves the exact
Submission and checks every Ranking value, including
aggregatorName, canonical target, target sub, and
self-authored By; route replacement still requires
confirmation. An unfollowed saved relationship is followed again only
through an explicit Follow action; the presentation checkbox never
changes that relationship. Discussion renders the checked original plain
text above Comments; Rankings supply only hasText while
that original is being retrieved. Feed age comes from the Ranking’s
Submission TAI claim; discussion uses the checked Submission TAI when
available, and Comment age comes from the checked Comment. Subdued grey
ⓘ buttons open the shared information dialog for feed or
Comment details without becoming navigation links. Endorsement,
identity, and per-aggregator name provenance use that dialog. Reply
moves the one composer directly below its parent and returns it to the
root position after submit or cancel.
The Aggregators action is the one compact global connectivity
indicator. It shows the active route count, appends ? for a
partial connection and X when none connect or attention is
required, and has no suffix when every route is connected. The one
global error surface carries details.
The browser does not narrate interlace rounds or add per-row synchronization prose. Transport failures reconnect independently with bounded backoff. Lace Timeout, Policy, PeerProtocol, Store, Integration, and Limit errors wait for explicit Retry.
The service owns one key, filesystem-backed Lace, loopback HTTP/WebSocket backend, public WSS address, exact sorted sub and operator lists, and one serialized control/Ranking/Thread work owner. Static assets are ordinary infrastructure and are not served by the aggregator backend.
Configuration is exact:
{
"listenAddress": "http://127.0.0.1:17443",
"publicInterlaceAddress": "wss:aggregator.example/interlace",
"dataDirectory": "/absolute/path/to/data",
"subs": ["rust", "science"],
"operators": ["V.<operator>.H3"]
}
The private backend accepts only loopback
http://127.0.0.1:PORT or http://[::1]:PORT;
production places authenticated WSS termination in front of it. The
proxy, backend leg, and service form the trusted confidential channel.
The public service must not expose the loopback backend directly.
Initialize and run an aggregator:
npm --prefix incubator/clay run aggregator:init -- \
--config /absolute/path/aggregator.json \
--listen http://127.0.0.1:17443 \
--public-interlace wss:aggregator.example/interlace \
--data-directory /absolute/path/clay-data \
--sub rust --sub science \
--operator V.<operator>.H3
npm --prefix incubator/clay run aggregator:check -- \
--config /absolute/path/aggregator.json
npm --prefix incubator/clay run aggregator:run -- \
--config /absolute/path/aggregator.json
Initialization prints one aggregator-advertisement target per
configured sub. An aggregator may publish that invitation as the target
of a self-marked #clay Submission whose title is a post
headline, whose non-null aggregatorName is its suggested
display name, and whose optional text is its blurb. The operator page
displays its browser-local record-by value before connecting. Add that
value to the sorted operators array and restart the
service. Removing it and restarting writes inactive private
authorization state. Configuration is startup-only; the aggregator
By-secret never enters the browser.
Participant CLI commands require --data-dir
globally:
key show
submit --sub SLUG --title TITLE [--target ADDRESS] [--text TEXT] [--aggregator-name NAME]
vote SUBMISSION Up|Down|None --sub SLUG
comment --sub SLUG --submission HASH [--parent HASH] --text TEXT
feed --sub SLUG [--sort popular|latest] [--period-end TAI]
follow INVITATION [--weight 1..100]
unfollow --aggregator RECORD_BY --sub SLUG
sync
MembershipRequest is never exposed or transferred from the aggregator
inbox, although Lace’s standard known-hash possession side channel still
applies. Membership is public evidence. Rankings and Thread membership,
labels, counts, scores, and omissions remain aggregator claims. A
directory Ranking curates an advertisement but does not establish its
self-authorship or endpoint authority. The advertisement Submission Mark
proves only control of its participant key; the exact checked
By and non-null aggregatorName must match the
compact Ranking claim and the invitation aggregator, and live WSS proof
must still match that key. Follow remains a reader-marked local record.
Original linked Submissions and Comments retain participant Marks after
copying.
Whole-record exposure is the peer-policy declassification boundary. An active operator receives complete private MembershipRequests and bounded unpublished content in that aggregator’s isolated operator Lace. OperatorAuthorization remains private and unexposed; ControlCommand and ControlOutcome remain private to the matching operator workflow. A public Membership effect discloses its originating command hash; a private operator-visible Moderation effect does the same. Neither exposes the command bytes. Route hints, sub slugs, and Follow weights do not grant authority. Production WSS requires a termination path whose proxy, loopback backend, and service are confidential, integrity-protected, and endpoint-confined for the carrier lifetime.
Generated dist/ is rebuilt from source and never
edited.
npm --prefix incubator/clay run check
npm --prefix incubator/clay test
git diff --check -- README.md incubator/clay