Clay

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.

Roles and authority

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.

Subs and browser addresses

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.

Exact records

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.

Submission

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.

Vote

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.

Rankings

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.

Follow

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.

MembershipRequest

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.

Membership

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.

Comment

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.

Thread

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.

Ranking and discussion projection

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.

Exchange and lifecycle

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.

Limits

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.

Enrollment and operator control

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.

Browser behavior

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.

Aggregator service

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

Security and disclosure

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.

Development

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