# autonomous.feedback as a product (2026-09)
#
# operation: prime
# Anyone can write a card about any site today; no install comes first. So the
# reader is the first user, the card is the free product and the marketing at
# once, and a site owner's first contact is a card that arrives by hand. Owners
# then climb: claim the site for free with one file, and pay for a desk that
# keeps every card. Nothing here sets a price, and no page may promise one from
# this file.

#>feedback_product_2026_09
#:cache #!product #!feedback #!marketing
#:operation #!prime
#:fixity #!experimental
#:layer #!pragmatics

@worker: ~"../../workers/autonomous-feedback/src/index.js"
@shell: ~"../../workers/lib/shell.js"
@inbox: ~"./cluster-feedback-inbox-2026-09.spw"
@pricing: ~"./pricing-lattice-2026-09.spw"
@plan: ~"../../.agents/plans/quest-workbench-feedback/PLAN.md"
@desk_flow: ~"../slices/feedback-desk-flow/index.spw"

operation = "prime"
fixity = "experimental"
source = `creator, 2026-09-22: market autonomous.feedback as a product; consider freemium, with a rewarded habit of screenshotting a filled form or rendered card and sending it to the site's owner by DM or in a post that names the site`

^"fb-001_reader_first"{
  id = "fb-001"
  claim = `The product does not wait for owners. /{host}/{kind} works for any public site whether or not its owner pasted anything, so growth starts with a reader who has something to say, not with an owner who installs a widget.`
  evidence = `validSubject() accepts any public hostname; nothing in the Worker checks that the site opted in.`
  consequence = `The owner's first contact is a card sent to them by DM or in a post. The form on their own site is the second step, not the first.`
  risk = `A reader can also send a card to a site that does not want feedback. See fb-005 for what keeps that humane.`
}[reg=facet]

^"offer"{
  reader = .{
    audience = `fans, peers, and customers of small sites who want to say one true thing without making an account`
    offer = `a card with your words on it, addressed to the site, that you send yourself`
    next_action = `autonomous.feedback/{host}/problem → write → Save image or Post → the owner receives it`
  }[reg=facet]
  owner = .{
    audience = `people who build or keep a small site (indie makers, artists, shops, open-source docs) and want honest notes without running a form backend`
    offer = `notes that already arrive as cards; a form in one paste; a claim with one file; later, a desk that keeps them`
    next_action = `receive a card → autonomous.feedback/{host} → Hang a form → publish the client file → ask for a desk`
  }[reg=facet]
  proof = `the card itself: a screenshot carries the site name, the note, the date, and autonomous.feedback/{host}`
  resonance = `a letter, not a ticket; plainly named kinds (Broken, Confusing, Missing, Wrong, then Question and Appreciation) instead of stars. "Autonomous" is literal: the note travels without the site's backend`
  copy_rule = `2026-09-22, creator: labels and prompts are direct and useful with no context, and the URL matches the label. The kind's title names the intent and is its slug (problem, suggestion, question, appreciation); its prompt says what to write; its example shows one. The earlier slugs (review, practice, brief, wonder) redirect and still work in request bodies and client files.`
  first_client = `spwashi.com: .well-known/autonomous-feedback.json in this repo, the Feedback card on /contact/ → autonomous.feedback/spwashi.com/problem`
  extension = `each card seen in a feed is an address where the next reader can leave one; the owner who receives a few by hand is the one who wants a desk`
}[reg=facet]

^"fb-002_ladder"{
  id = "fb-002"
  claim = `Four rungs, each earned by the one below. Only the last one costs money.`
  rungs = #[
    .{ rung = `reader` ; gets = `write to any site, a card, Share / Post / Copy` ; costs = `nothing` ; status = `landed 2026-09-22` }[reg=facet],
    .{ rung = `unclaimed site` ; gets = `the meter, an iframe or a plain form to paste; cards still travel by hand` ; costs = `nothing` ; status = `landed` }[reg=facet],
    .{ rung = `claimed site` ; gets = `the client file (fb-client-route): which kinds show, labels, theme, frame ancestors, and a "kept by the site" mark on its pages and cards` ; costs = `nothing; publishing the file is the proof of control` ; status = `the file landed 2026-09-22; the "kept by the site" mark is still proposed` }[reg=facet],
    .{ rung = `desk` ; gets = `a queue that keeps every card, an owner inbox, a digest, and reply cards` ; costs = `the paid tier` ; status = `2026-09-23: landed as the desk flow (@desk_flow, the auditable contract). The file asks (queue.want), the operator opens (DESK_HOSTS), and an owner key published as a hash (inbox.key) or the operator token reads. New cards wait three days; owners save up to SAVE_LIMIT; the rest compact to tallies with no words. Digest, webhook, reply cards, and the appreciation wall are still proposed.` }[reg=facet]
  ][reg=set]
  why_claim_is_free = `Claiming costs the owner one file and costs us one cached fetch. It turns a stranger's address into a customer we can name, and it is the verification the desk needs anyway.`
  desk_needs = #[`a claimed site (fb-client-route as http-01-style proof)`, `an identity provider for the owner`, `storage with retention rules`, `rate limits on public intake`, `moderation for stored notes`][reg=set]
}[reg=facet]

^"client_route"{
  id = "fb-client-route"
  claim = `A client site publishes https://{host}/.well-known/autonomous-feedback.json. autonomous.feedback fetches that URL when serving /{host} or /embed/{host}. The file configures only that host.`
  url = `/.well-known/autonomous-feedback.json`
  schema = `autonomous-feedback.client.v0`
  fields = #[`schema`, `host`, `name`, `intro`, `kinds`, `labels`, `from`, `button`, `note.min`, `note.max`, `frame.ancestors`, `theme.mode`, `theme.background`, `theme.text`, `theme.accent`, `theme.corners`, `theme.font`][reg=set]
  unlocks = #[`which kinds show and in what order`, `per-kind title, prompt, and example`, `a display name, an intro, and the button text`, `an optional or required name field printed on the card`, `note length bounds within 1–2,000`, `frame ancestors beyond the host and www`, `colour tokens, mode, corners, and font on the form, the frame, the card, and the saved image`][reg=set]
  refused = `theme.href and any HTML: the CSP blocks outside stylesheets, and tokens cannot inject.`
  queue_want = `2026-09-23: queue.want is a request, counted as fb-004 demand. It keeps nothing until the host is also in DESK_HOSTS and a reader exists. States and copy: @desk_flow desk_states, copy_contract.`
  landed = `2026-09-22: workers/autonomous-feedback/src/config.js; /{host}/config.json reports what applied; /start shows the status and the applied or starter file`
  defaults = `Missing file, timeout, non-JSON, or a host field that does not match the URL host: keep the free behavior. Meter, iframe, four kinds, slip, queue unattached.`
  check = `Fetch the file from the host in the path only. Do not apply lore.land's file to spwashi.com. Do not read it from a query string.`
  role_in_ladder = `The file is the claim rung of fb-002: its presence is the proof of control, and queue.want is the owner asking for a desk.`
  fetch_guards = `isPublicSite() before any fetch; 2.5 s timeout; 16 KB cap; a redirect to another host is ignored; cached per host for 5 minutes; text below 4.5:1 or accent below 3:1 contrast falls back to the defaults`

^"collect_guard"{
  id = "fb-collect-guard"
  claim = `Public intake is capped. Eight posts and twenty meter checks per address per ten minutes. A token, bearer, or password in the client file is refused and not echoed. INBOX_READ_TOKEN, when set on the worker, is compared in constant time; a match reads the queue when one is bound, and returns not_draining when none is. No token is accepted from the query string.`
  landed = `2026-09-22: workers/autonomous-feedback/src/intake.js`
  unset_token = `Missing Authorization stays 401. Any bearer while the secret is unset stays 501, even with a queue bound: no secret means no reader. A wrong bearer once the secret is set is 401.`
  unset_token_fixed = `2026-09-23: the D1 desk had made an unset secret read and clear the queue for any bearer; inboxAccess now returns "unset" and the route answers 501.`
}[reg=facet]
}[reg=facet]

^"fb-003_reward_loop"{
  id = "fb-003"
  landed = #[
    `the card is designed to be screenshotted: the kind, the site's name large, the note, an optional writer name, the date, and the address`,
    `Save image draws the card to a 1080-wide PNG on the device (the share sheet with the file where supported, a download elsewhere); Share, Post on Bluesky, Post on X, and Copy text sit beside it, and any of them stamps the card "Shared" and announces it`,
    `post text is prefilled with an excerpt, the kind, the site, and the address; the writer edits it before it goes anywhere`
  ][reg=set]
  honest_limit = `The stamp records that the writer reached for a channel, not that anything arrived. Say "Shared", never "Delivered", until a desk can prove delivery.`
  writer_rewards = #[
    `now: the craft of the card — it should look good enough that people want it in their feed`,
    `candidate: a frame view that shows the card alone, sized for a phone screenshot (square and 4:5), so screenshotting is the obvious move`,
    `candidate: a card link that carries the note in the URL fragment, so a card can travel as a link without being stored; the fragment never reaches the server, so the page renders it client-side`,
    `desk: a reply card from the owner, the one reward that proves the note landed`
  ][reg=set]
  owner_rewards = #[
    `now: notes arrive already legible, in the owner's own DMs, with the kind and prompt attached`,
    `claimed: the "kept by the site" mark tells readers the owner is listening, which draws better notes`,
    `desk: a private count of cards received, and every card kept in one place — never a public leaderboard`
  ][reg=set]
  trade_off = `A fragment link keeps the note off the server but gets no link preview. A preview image needs the note in the query string, which reaches request logs. Choose per channel; never claim "nothing stored" for the query form without checking the logs.`
}[reg=facet]

^"fb-004_measures"{
  id = "fb-004"
  claim = `Know whether the loop turns without keeping anyone's words.`
  count = #[
    `cards issued per week, by kind — a counter, not the note`,
    `share-row reach by channel (Share, Bluesky, X, Copy), counted at the click, no content attached`,
    `visits to /{host} and /{host}/{kind} arriving from social referrers — the screenshot or link bringing the next reader`,
    `client files first seen per week — owners who claimed, the conversion that matters`,
    `queue.want requests — owners asking for the desk before it exists`
  ][reg=set]
  refuse = `note text, hostnames paired with writers, or any per-writer history in analytics`
}[reg=facet]

^"fb-005_guardrails"{
  id = "fb-005"
  dm_first = `the lede names a DM before a post; criticism sent in public can read as a callout, so the page never makes posting the default`
  signed_by_sending = `The Worker keeps no author, but a card only travels through the writer's own account. Accountability sits in the channel the writer chose. Keep it that way: no anonymous relay that sends cards for someone.`
  no_handles = `the Worker never looks up or guesses an @handle. An owner may list their own under contact (bluesky, x) in the client file; the card page then offers Message buttons that open the profile, since no link can fill in a DM, and adds the handle to the Post links as a mention. 2026-09-22, creator: "there should still be a way to DM me on BlueSky or X".`
  unclaimed_wording = `On an unclaimed site the page says the owner will only see a card if someone sends it. Do not imply the site asked for notes.`
  no_storage_claims = `every storage sentence follows deskState: "Nothing is stored" wherever no desk is open, "kept for {site}" only where one is, and "unless the site has an inbox" where the host is not known yet. Never tell a writer a card was kept for an owner who cannot read it.`
  no_rank = `no star rating, score, or leaderboard on any card or page`
  opt_out = `a claimed site can hide kinds or all public cards through its client file; honor it before rendering`
  infrastructure = `public spwashi.com pages do not describe who runs the cluster (workers/cluster.json note)`
}[reg=facet]

^"fb-006_search"{
  id = "fb-006"
  claim = `Social carries cards; search should carry owners. Today search carries nothing: robots.txt disallows every path, and every page sends noindex, nofollow in a header and a meta tag.`
  index = #[
    `the homepage, rewritten as the answer to "a feedback form for a static site, no backend"`,
    `one page per kind of note (wonder, review, practice, brief) as an idea worth linking to, with a sample card`,
    `a short "how it works" that says plainly what is and is not stored`,
    `paste guides only where the steps really differ by host (plain HTML and GitHub Pages, Neocities, Carrd, Squarespace), each with the snippet that works there`
  ][reg=set]
  never_index = #[
    `/{host} and /{host}/{kind}: a page about every site on the web is thin, unasked-for, and reads as speaking for owners who never opted in`,
    `/embed/*, /meter, card pages, /climate.json`,
    `claimed sites, until the owner's client file asks for it explicitly`
  ][reg=set]
  mechanics = `robots.txt allows the indexed paths only; indexed pages drop the noindex header and meta; canonical URLs, a sitemap of those paths, a homepage share image showing a sample card, and SoftwareApplication structured data with no invented rating`
  voice = `Search copy states the job in the searcher's words first ("feedback form", "no backend", "static site"), then the site's own imagery. Titles stay honest: no "best", no "#1".`
  risk = `Indexed guides are marketing pages on a cluster domain; keep spwashi.com out of them (fb-005 infrastructure).`
}[reg=facet]

^"fb-007_utility"{
  id = "fb-007"
  claim = `The design explains the product before it shows it. The homepage never shows a card, which is the product and the reason to paste the form.`
  proposals = #[
    `show a sample card on the homepage, the one image that explains everything`,
    `preview the embed live under the snippet as the owner types their site, so they see it before pasting`,
    `the meter also looks for the form on the site's page and says whether one is already hung, which feeds fb-004's claim count without asking anyone`,
    `a "Save image" button that draws the card to a PNG on the device, because screenshotting is easy on a phone and fiddly on a desktop`,
    `an optional "who keeps it?" field on the card page where the writer types the owner's handle themselves; it goes into the post text only, never stored or looked up (fb-005 no_handles)`,
    `fix the pasteable form: its textarea has no label, so the snippet teaches owners an inaccessible form`
  ][reg=set]
  order = `the label fix first (a defect in what we hand people), then the sample card and live preview (they sell the paste), then Save image (it serves the screenshot habit), then the meter's form detection`
  landed = `2026-09-22: labelled snippet, sample card, /start with live codeblocks and preview, Save image. The meter's form detection and the "who keeps it?" field are still open.`
}[reg=facet]

^"price"{
  status = `unset`
  unit = `per claimed site (the host), not per seat or per note — the address is the identity everywhere else in the product`
  lane = `@pricing thesis: new offers inherit an existing lane. The desk would inherit the ecosystem lane (free–$500), not a new SaaS price family. Compare there before naming a number.`
  copy_now = `2026-09-23: the homepage "What it costs" names three rungs without a number: any site, free; your site (the file), free; an inbox, on request, which keeps new cards three days and lets the owner save some.`
  file_free_desk_paid = `2026-09-23, creator agreed: anything expressible in the public client file is free (it costs one cached fetch and spreads the form); anything that makes us hold state or push outward is the desk. SAVE_LIMIT and retention past three days are the natural paid dimensions.`
}[reg=facet]

^"fb-008_subjects_and_threads"{
  id = "fb-008"
  source = `creator, 2026-09-23: "the form would be useful for immediate feedback and could allow for more thoughtful or tailored responses; a lot of creators receive the same kind of feedback around the same subjects they represent." Subject comes from the referrer or the flow, not the address.`
  claim = `Two axes. Kind says what happened (a short generic set). Subject says what it is about: the creator's own map of their work, declared in the client file. Kinds alone put every note in six buckets and give the reader no help placing what they mean.`
  speeds = #[
    `immediate: subject from the page (subjects[].paths, prefix match on the detected page) or a chip; kind a chip; one textarea; send`,
    `recurring: a subject's threads are what the creator hears often, each with the creator's current stance; the reader taps one, reads the stance inline, and adds words or a bare "count me too"`,
    `thoughtful: "Say more" opens the subject's asks, two or three tailored questions; answers render in the card and the slip under their questions`
  ][reg=set]
  reward = `a thread's stance shows on the card page after sending, labelled as written before the note: the reply-card reward of fb-003, delivered through the file with no storage and no desk`
  schema_v1 = `additive to client.v0: subjects[] { id, name, paths[], prompt, kinds[], asks[], threads[] { id, name, stance, link } }. Limits: 12 subjects, 6 threads and 3 asks each, prefix paths, text caps as elsewhere. routes stays for page names.`
  counts = `tallies gain subject and thread; "Folios · order-by-card · +7" is the count a creator can act on. The page stays on the card.`
  refuse = #[`reader-typed tags (they fragment counts and put naming on the wrong person)`, `required asks (everything past the one textarea is optional)`, `a public most-requested list (counts belong to the creator)`][reg=set]
  order = `prime (this) → form: subject chips, thread chips with inline stance, Say more; browser approval at each step → card subject line, asks as sections, stance on the card page → tallies by subject and thread → a subject builder on /start`
  status = `2026-09-23: form landed as a demo for approval (subjects, threads with kind and stance, asks, subject example, thanks line); counts by subject and thread landed (desk flow fd-11, fd-12).`
  input_calculus = `creator, 2026-09-23: "what could make the path of least resistance also feel the most rewarding for the commenter while being useful in aggregate later." Each input is priced by the writer's cost and the owner's information: evidence-only defaults (page → subject; link → kind; no preselected kind, so untouched notes stay unsorted instead of inflating Broken); the fast lane first (a common note is one tap, the richest count, and it returns the creator's stance); words optional after a tap, and counted as worded or one-tap; "Just saying thanks" as the cheapest honest positive signal, rewarded by the owner's thanks line; asks tuned to what each surface's visitors can uniquely say (prior knowledge on explainers, device on components, intent on services).`
  type_is_enough = `2026-09-23: a chosen kind is a complete submission. note.min applies only when no type is chosen, so eight characters are not a second price on top of the type. A type with no detail is stored worded 0, and the note text says it is a count. Words are stored only when the reader writes them. The form's outcome line states that split before they send.`
  landing_map = `spwashi.com subjects follow where visitors land: topics (106 pages) and design/components (186) got subjects of their own beside Getting started, Spw, Recipes, RPG Wednesday, Folios, and Services; the longest path prefix wins, so Spw and Folios sit inside Topics and design.`
  owner_communication = `the inbox opens with the week: grouped counts, how many came in their own words, thanks apart, what is counted within a day, and map hints where notes keep missing a common note. Compaction is a summary the owner can act on, not a deletion log.`
}[reg=facet]

^"fb-009_serial_fiction"{
  id = "fb-009"
  source = `creator, 2026-09-23: "what could make this site more useful for authors, or more useful for readers of lore.land", pointing at the lore.land repo (~/Development/active/lore.land).`
  claim = `Fiction feedback is not QA. Readers of a serial want to say a line stayed with them, that they have a theory, or that they have a question about the world; an author needs where a chapter lost someone and what readers expect next. The broken/confusing/missing/wrong kinds cannot hold that; subjects and common notes can.`
  lore_land_today = `13 chapters built from book/content/chapters/*.json; sections have titles but no ids, and the prose is rendered by script into #chapter-content, so no passage has a stable anchor. Topics, lore items (the field-notes collector), a Scriptorium with patron seals, releases on the 13th and 26th. No .well-known/autonomous-feedback.json and no feedback link yet (checked 2026-09-23).`
  readers = #[
    `passage notes: select a line in the chapter runtime → autonomous.feedback/lore.land?at=/book/chapter/NN/#quote=… ; the quote rides in the fragment, so no server or log sees it until the reader sends; the card shows the quoted line with the note under it`,
    `sealed theories: a "Theory" common note makes a card sealed until the next release; the card carries an HMAC of note, date, and host, so autonomous.feedback/verify/… can later confirm it was filed before the chapter, with nothing stored; shared sealed cards spoil nothing`,
    `questions that become canon: "A question about the world" carries a stance for the frequent ones; an answer promoted to a lore item credits the reader who asked`,
    `feedback at the chapter's end, never mid-prose: a frame after the related routes ("What stayed with you?") with the chapter taken from the referer`
  ][reg=set]
  authors = #[
    `split: "page" on a subject keeps counts per page inside one subject, so 13 chapters (over the 12-subject limit, and sharing one set of common notes) read as "Ch. 7: 4 stayed with me, 2 lost me, 3 theories"`,
    `cadence: [13, 26] in the file makes the digest and compaction follow release windows ("Since the Sep 26 release…") instead of weeks`,
    `preset: "serial" brings the fiction common notes (a line stayed with me; lost the thread here; a theory; a question about the world; a typo or continuity slip), sealed theories, and cadence in one line; it is file-expressible, so free under file_free_desk_paid, and it is the feedback half of selling the reading room to other authors (@pricing series-home)`,
    `spoiler-aware sharing: theory and late-chapter cards share blurred, chapter shown and note hidden until tapped; appreciation-wall entries (when that lands) carry chapter tags`
  ][reg=set]
  refuse = #[`pay-to-be-heard: patron seals may shape credit in the Scriptorium, never inbox priority`, `reading analytics: where a reader put a chapter down is asked ("Lost the thread here"), not tracked`][reg=set]
  order = `1 lore.land's .well-known file (Chapters, World, Characters, Scriptorium; fiction common notes; a thanks line in the creator's voice), usable with what is built → 2 split: "page" and cadence-aware digests here → 3 in lore.land: section ids in build-chapters.mjs, the chapter-end frame, select-to-quote in the chapter runtime → 4 sealed, signed theory cards (needs a signing secret; demo for approval first)`
  boundary = `lore.land is its own repository with its own CLAUDE.md: read it before editing there, and keep its commits apart from this one.`
  status = `2026-09-23: prime. Nothing built.`
}[reg=facet]

^"fb-010_outputs_and_loops"{
  id = "fb-010"
  source = `creator, 2026-09-23: "mind feeds, reports, digests, cards, prompts; consider loops."`
  claim = `What the product sends back out decides whether writing a note was worth it. Every artifact names its reader, what it may carry (words or only counts), whether it is public, and what it costs to keep; every loop ends in something the writer can see.`
  artifacts = .{
    cards = #[
      `note card (landed): the writer's own artifact; carries their words; public only when they share it; stores nothing`,
      `stance on the card page (landed): the owner's answer to a common note, shown after sending; lives in the file`,
      `sealed theory card (fb-009): signed, sealed until the next release, verifiable later without storage`,
      `reply card (desk): the owner answers one card; the writer sees it by revisiting their card's address; needs a stored answer, so paid`,
      `canon credit card (fiction): a question answered as a lore item, crediting the asker; public, and only with the asker's yes`,
      `release card (owner): counts only ("Ch. 7 in your words: 12 notes, 5 theories"), made by the owner to share; never words, never a ranking`
    ][reg=set]
    digests = `owner-private (landed in the inbox): the week, or the release window with cadence; grouped counts, own-words counts, thanks apart, due warning, map hints. Pushed later by webhook or email as a desk feature.`
    reports = #[
      `per-release report (owner-private, from tallies): resonance and lost-the-thread by chapter or subject across windows`,
      `export (owner-owned): tallies as CSV or JSON; the owner's counts are theirs to take`,
      `audit report (operator): the desk-flow queries in .spw/slices/feedback-desk-flow run against the remote D1`
    ][reg=set]
    feeds = #[
      `public feed (file-expressible, free): the owner's stances and canon answers as Atom or JSON Feed, so "what the author has said" can be followed; changes when the file changes`,
      `private feed of new cards: open question; feed readers cannot send a bearer, and a key in a URL is what fb-collect-guard refuses; prefer the webhook`
    ][reg=set]
    prompts = #[
      `reader prompts (landed): the question-as-label, the asks, and the common notes are prompts; the chapter-end frame asks "What stayed with you?"`,
      `owner prompts (landed): map hints ask the owner to add a common note where notes keep missing one`,
      `agent prompts: the digest as a prompt for the owner's own agent ("readers said this about Ch. 7 in counts; draft lore items for the top three questions"); counts only unless the owner adds saved cards; the same shape as spw.quest's prompts.json`
    ][reg=set]
  }[reg=facet]
  loops = #[
    .{ name = `answer` ; path = `question → stance or reply → the writer sees it on the card page or at their card's address → fewer repeat questions` ; closes_for = `the writer` }[reg=facet],
    .{ name = `map` ; path = `notes without a common note → digest hint → owner adds one → the next reader taps it → a count the owner can act on` ; closes_for = `the owner and the next writer` }[reg=facet],
    .{ name = `canon` ; path = `a world question → an answer promoted to a lore item with credit → public feed and credit card → shared → new readers arrive at the chapter` ; closes_for = `the asker, publicly` }[reg=facet],
    .{ name = `theory` ; path = `a sealed prediction before a release → the release → reveal and verify → "I called it" shared → the next chapter's theories` ; closes_for = `the writer, provably` }[reg=facet],
    .{ name = `release` ; path = `cadence digest and report → the author writes the next chapter knowing where readers were lost → release card → readers return` ; closes_for = `the author and returning readers` }[reg=facet],
    .{ name = `thanks` ; path = `one-tap thanks → the owner's thanks line → thanks counted apart in the digest` ; closes_for = `both sides, cheaply` }[reg=facet],
    .{ name = `agent` ; path = `agents file findings under a writer label → digest → owner fixes → the agent checks again` ; closes_for = `the owner` }[reg=facet]
  ][reg=set]
  hygiene = #[
    `every loop ends in something the writer can see; a loop that only feeds the owner is a funnel, not a loop`,
    `words leave the inbox only by the writer's own share or the owner's explicit choice; digests, reports, feeds, and release cards carry counts`,
    `public outputs are opt-in and spoiler-aware; credit names a reader only with their yes`,
    `no loop rewards volume: no leaderboards, no "top readers", no streaks`
  ][reg=set]
  order = `already landed: note card, stance, inbox digest, map hints, reader prompts → next free: public stance feed, release card, agent prompt from the digest → then desk: webhook digest, reply cards, export → fiction: sealed theories, canon credit (fb-009)`
  status = `2026-09-23: prime.`
}[reg=facet]

^"open"{
  questions = #[
    `Does the "kept by the site" mark need the desk, or should a claimed site earn it for free?`,
    `Should an owner be able to answer a card before the desk exists, as a card they post themselves?`,
    `Which comes first after claiming: the frame view for writers, or the fragment link?`,
    `fb-009: does a sealed theory reveal itself on the release date, or only when its writer shows it?`,
    `fb-009: should a credited canon answer name the reader by the handle on their card, or ask first?`
  ][reg=set]
}[reg=facet]

do_not_promote_until = `a card about a site outside this cluster reaches its owner by hand and the owner answers — a reply, a pasted form, or a published client file — observed once`
validation = `node --test workers/autonomous-feedback/test/routes.test.mjs; post a note to /{host}/wonder and screenshot the card at pocket and desktop; once fb-client-route lands, a planted client file changes the kinds on /embed/{host} and a mismatched host field does not`
