Ctrl K
ComunidadGuías…

NEWS

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
Quilzo user-visible changes.

Newest first. Only what somebody running this would notice; the commit log
has the rest.


Unreleased
==========

** Governance

*** A publish of the draft, from the command line or over MCP, puts live
    exactly the commit its checks were asked about; a save landing while
    they ran went live unchecked.  For a day after an administrator is
    removed, the one left cannot approve anything alone (their own
    requests, playbooks, policy changes, installs, the first token) unless
    a second administrator holds the role again; a store that only ever had
    one administrator is unaffected.  Approving a group mapping that makes
    administrators shows every live provisioning token, who made it and how
    it was approved (People, or quilzo pending show), and a token made
    while such a mapping waits takes two administrators.  A chatbot asked
    inside an agent's run is charged to the run's budget and its person.

** Who has access

*** Quilzo reviews the access only it grants.  Every
    access.review.every (ninety days, or the organisation's AC-2 period
    where that is shorter), an administrator keeps or revokes each grant
    made by hand, what each identity provider group means here, and each
    long-lived token: quilzo access review start, items, decide and close,
    or Review who has access on People.  Access unused for ninety days is
    listed first and proposed for revoke; keeping privileged access needs a
    reason; nobody decides their own access or a group they are in; the
    reviewer is a person, signed in with two factors on People; a revoke
    takes effect at once and never leaves the store without an
    administrator.  Closing writes the evidence, and the posture finding
    access.review-due, with a mail to the security contact, says when one
    is overdue.

*** When the identity provider says a session is over, it is.  quilzo
    signals connect binds an OpenID Shared Signals stream (CAEP and RISC)
    from the OIDC or SAML provider people sign in with, received at POST
    /sso/signals: a revoked session or changed credential ends that
    provider's sessions, a compromised credential also asks the person to
    sign in again and tells the security contact, and a disabled account
    is suspended as SCIM suspends one (and restored when enabled again).
    The last administrator is never suspended by a signal; at most 20
    people an hour are acted on, and the rest wait for the provider to
    send them again.  quilzo signals status and the Sign-ins
    screen show the stream, and signals.stream-silent says when it has
    stopped answering.  The shield can contain it ("signals").

** Security

*** A model's draft never goes live unapproved.  The admin's Publish
    put an agent-written draft live when no approval count was set, and
    the command line published past an approval policy it could not read;
    every surface now holds a model's draft for a person, holds any draft
    for the people approval.required_humans asks for, and refuses to
    publish when the policy cannot be read.

*** Access is decided as it stands now: both content APIs re-read the
    access rules, /mcp and token exchange see a revoked token at once, and
    a token's last use there is written down.  A throttled address slows
    only its failures, so one guesser behind a proxy locks nobody else
    out.  A passkey whose counter went back is refused and recorded as a
    copy.  A permission granted alone reaches the commands its work needs.
    A group mapped to auditor grants it until a day (scim map --until).  A
    reader lists their own grants and tokens; whoever may grant, everyone's.

*** SCIM and Shared Signals never leave a store without someone who can
    grant access, even when the last administrator is deactivated and
    renamed in one request; events are acted on in the order they were
    issued, a replayed event touches only sessions open when it was sent,
    and events past the hourly cap wait instead of being lost.  The scanner
    finds every kind of secret Quilzo makes.

*** One CSV guard for every CSV Quilzo writes, the subscriber's address
    included, and an upload check that finds a formula wherever a
    spreadsheet would run it.  Importing thousands of same-titled items
    takes time in proportion to them, not minutes.

** Found by using it as people do


*** The chatbot shows a sentence as verified only when it says what its
    passage says: one whose figure, time of day, duration, price, "not"
    or opposite word turns the passage around is taken out before the
    visitor sees it.  Research reports are checked the same way.

*** A webhook whose receiver is restarting gets the event: the second and
    third attempts wait one and then four seconds, and a receiver that
    answers 429 or 408 is tried again like one that answers 5xx.  An
    integration tool that says it failed makes quilzo integrations call
    exit non-zero and is a failure on the record; a call refused before it
    is sent is recorded as denied.

*** Asking any command for help (-h, -help, --help, --h, with or without
    a value) prints that command's help and does nothing else; quilzo init
    --help created a store, and brand init --help and token keygen --help
    wrote files.  auth revoke's help names both its arguments, auth list,
    token list and token stale keep long names in their columns, and the
    read-only API's refusals name the flags that turn writes on.

*** Single sign-on as organisations run it: SAML refuses an address the
    provider says it has not verified (saml add --verified-attribute; the
    Keycloak preset asks for email_verified), an identity provider on the
    organisation's own network is reached where sso.reach declares its
    range (a weaker setting: a reason and a second administrator), and
    signing out offers signing out of the OIDC provider too.  The profile
    says what you hold and where, as your session holds it.  Refusals say
    why: plain http for what the connection is for, the command that
    needed a token, a refused SAML response once and by its provider's
    name, and where oidc status looked for the client secret.

*** The admin says how long ago one way ("3 hours ago", "1 day ago",
    "1 minute"); the Today screen said "3 h ago" beside "3 hours ago", and
    "1 minutes" appeared where a minute was meant.  The help lists the
    public site's API flags under quilzo site, where they work.

*** A duration setting takes days and weeks (90d, 13w), and Quilzo says a
    duration in days where it said 2160h or 2400h0m0s.  An access review
    closes only once its evidence is written.  A posture finding about a
    token names the token, and the Security screen says whose it is.

*** Agents: a decide step takes an object or a list as its state, a
    person's answer to an escalated question keeps what the decider
    settled, agent commands take their flags either side of the file or
    name, and a finished run says what the agent concluded.

** Installing

*** deploy/compose.yaml is a reference stack that works as written: the
    admin over HTTPS from a Caddy of its own on 127.0.0.1, the published
    site behind Caddy, and a log writer of its own that holds the only
    writable copy of the audit log.  Following deploy/README.md word for
    word gives a store on a volume, a design before the site starts, an
    enrolment link that names the published address, and a first token.
    The image's entrypoint names its store (/srv/store), so no command can
    lose it, and any command that would make a store on a container's own
    layer says it would go with the container.  An image built from a tag
    says its version.

** Kept features made right

*** Self-defence tells its owner: a hold on the shield's playbooks no
    longer silences its alerts (a decoy touched during a hold is still
    told), and holding and letting the playbooks act again are both told.
    Alerts that have nowhere to go are a posture finding (alerts.unrouted),
    and a mail that could not be sent is a failure on the record.  A flaw in
    this build is told once a day, not every hour.  Sign-in mails say what
    happens next.  A break-glass recovery is told to the security contact.
    A missing or month-old catalogue of exploited flaws is said by self
    check and is a posture finding (self.kev-missing).

*** Compliance evidence says what the code does: compliance summary counts
    the quantum-broken algorithms it generates (it printed "none"), the
    control and pledge statements say what is automatic and which server
    checks itself, a FedRAMP indicator nothing checks is "stated", and
    posture scan --oscal keeps accepted risks as not satisfied, with who
    accepted them.  A timestamp is over the full live commit, the subject an
    anchor commits to, so each shows the other; anchors say whether they are
    of a publication or a log head, and quilzo anchor export writes the .ots
    files the ots client reads.  Sending a digest out (anchor submit and
    upgrade, timestamp stamp) takes grant and is on the record.  The
    classification marking gate refuses what it cannot read, instead of
    letting it through.

*** Rooms keep their word: erasing a message rewrites the room so no
    byte of it is left on disk (copies already in backups age out with
    them), a room's retention is applied by the admin's sweep, a closed or
    direct room is read only by its members, reactions are kept (quilzo
    room react), and the room list shows what is unread.

*** The studio follows a revoked token without a restart, records what it
    uploads as the token's holder, and records sound only when somebody
    ticks "Record sound too", with a notice to ask the people it will hear.

*** Share links keep a digest of their secret, not the secret, and are
    matched in constant time; links already given out keep working.
    quilzo desktop consent fails when it could not ask.

*** A research plan reads as signed only when its signature checks.
    Making pictures, video and voice (media generate, film, speak,
    transcribe) needs use-models, as assist and evolve run do.  A recording
    over the 25 MB a transcription service takes is transcribed from its
    sound alone when ffmpeg is installed.  quilzo cell warm makes the
    snapshot every cell session restores.  The chatbot and research find
    figures with one detector.

*** The shield can turn down bookings, the newsletter and the fediverse,
    which it was asked about and could never switch off.  Replica answers
    are private to shared caches.  An agent card that does not validate is
    left out of a static copy instead of failing it.  The Permanent web
    screen gives the same content identifier as quilzo ipfs id, and no
    longer bundles members-only or scheduled pages.  The public /mcp
    negotiates the MCP revision as the admin's does.

** Fixes from a security review of the whole program

*** The admin and the site start for the account the store belongs to.
    `quilzo init --admin NAME` ends by telling its reader to start the
    admin, and the systemd unit and the image start the servers without a
    token; all three were refused with "publish needs a verified identity"
    once the store had an administrator.  The account that owns the store
    (or root) may now start `serve` and `site` without one; anybody else
    still needs publish, and is told whose store it is.  Everything else
    that account does on the store still needs a token.

*** The account menu no longer scrolls.  A rule meant to enlarge small
    links in the bar reached the menu's items, so the two items overlapped
    and the menu showed a scroll bar beside two choices.

*** `go install github.com/quilzo/quilzo/cmd/quilzo@vX.Y.Z` makes a
    binary that says it is vX.Y.Z; it said "dev".

*** Outbound requests report the status that came back (they reported 0),
    a staff notice to a chat service that answers 202 or 204 is delivered,
    federation delivery gives up on a receiver that drips its answer, and an
    agent's web_fetch is held to the hosts it declared on every redirect,
    not only the first and last address.

*** Member sign-in returns only to a page on this site; a "next" address
    with a tab or newline in it went elsewhere.

*** The public site's passkey sign-in is limited per address and its
    prompts are bounded; one inbox is sent a newsletter confirmation once an
    hour however its address is spelled (dots and +tags); the files every
    form keeps together have a ceiling (1 GiB), and a form past it says so;
    search scores are computed over the pages a visitor may read, so they no
    longer reveal whether a hidden or embargoed page contains a word.

*** A path-scoped session (an author on /blog) is held to its path in bulk
    actions, the pages list, notes, preview links and Today's figures; a
    peer is sent the whole store only on a credential for the whole store;
    exporting the form fields marked sensitive asks about the session, as
    purging them does.

*** /signout from a link the person did not follow, an image or a frame
    asks "Sign out?" instead of signing them out.  The menu's Sign out still
    signs out at once.

*** A submission through the share sheet is audited, counted and alerts
    staff, as the same form's own route always has.

*** Reading untrusted files is bounded: a PDF's streams together inflate
    to at most 64 MiB, YAML refuses nesting deeper than 64 and reads long
    values in one pass, a calendar's folded lines unfold in one pass, and a
    CSV row wider than a sheet may be is refused before it is read.

*** Promoting into production is publishing.  `quilzo env promote …
    production` and the Publishing screen's promote put a draft live without
    the publish checks (review approvals, provenance, accessibility,
    marking) and fired no published event.  They now run every check on the
    commit being promoted, record the publish and notify as publishing does,
    and `env promote` takes publish's options.  Approvals are checked on the
    commit being published, so staging cannot go live on a later draft's
    approval.

*** Dual control holds where one administrator could act alone: an
    enrolment link for an administrator, and the provisioning token while a
    group mapping makes administrators, each wait for a second
    administrator, and are then received once by whoever asked (People, or
    `quilzo pending redeem`).  A session confined to /@security, or capped
    at reader, can no longer change the shield, the organisation's policy,
    or another administrator's standing.

*** Agents hand work on as safely as they do it: a supervisor's delegate is
    reviewed as the supervisor is, its writes are marked as a model's work,
    it acts and is charged for the person who started the run, it gets only
    what is left of the run's tokens and money, and a hand-off the reviewer
    refuses waits for a person who is shown where it goes and what it is.
    Pausing a run no longer gives back what its steps and delegates spent,
    and a decider's and the reviewer's model calls are charged to the run.

*** A tool step counts as a write when its integration says it writes, not
    only its manifest, so it is never batched with reads or approved by its
    own requester.  Another person's remembered procedure is their words in
    the run that recalls it, and taints it.  A delegate holds only the tools
    its supervisor declares by name.  quilzo_check runs only the checks that
    gate publishing.

*** Apps connected over OAuth: removing an allowed host ends its
    connections however the app spells the host, and a code redeemed after
    its app was removed connects nothing.  A refresh asking for less
    narrows its access token, never the grant.  SCIM hears a deactivation
    however its attributes are spelled, refuses what it cannot apply, and a
    stale change can no longer undo a concurrent deactivation.  HTTP message
    signatures are bound to this server's own address.  Quilzo's A2A client
    speaks A2A 1.0 where a card offers it, so one Quilzo agent can call
    another.  The Authorization header is read in any case of its scheme,
    and DPoP, agent credentials and federation share one bounded replay
    cache that a flood of proofs cannot fill.

*** Pages an agent reads with web_fetch, OpenAPI documents an agent is
    declared from, and WordPress and Ghost exports are read in time in
    proportion to their size: a crafted page, a self-referring request body
    or a fan of references could take minutes, gigabytes or the process
    itself.

** Quilzo defends itself; a security team's own tools are no longer in it

*** A security team's tools left Quilzo: collecting from other systems
    (connect, collect, inbound, source, spool, telemetry and the Feeds and
    Events screens), detections, Sigma, correlations and the trial ring
    (detect, sigma, correlate, proving, hunt, triage), the finding queue and
    risk (finding, analyst), cases and incidents with their regulatory
    clocks, actions on other platforms, automations, vulnerabilities (vuln,
    sca, sarif, appsec, feed, reachability), the estate and workforce risk,
    vendors, access reviews, reminders, notices to customers, work tracking,
    estate canaries, and the MCP tools over all of them.  Their screens are
    gone from the admin, and the Security operations menu is Protection:
    Shield and Sign-ins.  Quilzo keeps what it needs to defend itself, and
    hands its audit log to any SIEM that reads OCSF, as it is written
    (siem.stream.url).

*** A risky sign-in has to confirm it is its person, built in and on.
    Impossible travel, a session copied to another device or country, Tor
    or a hosting provider, an administrator in a country they have never
    signed in from, or three unfamiliar things at once: the session is
    held until a passkey or a fresh single sign-on confirms it, and the
    security contact is mailed about the worst of them.  This was a set of
    automation rules nobody had until they added them; it is now the
    setting signin.adaptive (NIST SP 800-53 IA-10), on by default, and off
    is a weaker setting that takes two administrators.  A person reporting
    that a sign-in was not them ends every session they hold.

*** Posture drift is on the record.  A check that starts failing is
    written to the audit log once (posture.drifted), and again when it is
    fixed (posture.restored); a high or critical one is mailed to the
    security contact.  It used to open a finding in the queue that left.

*** The shield's "open a case" asks for one in the audit log
    (shield.open-case, with the playbook, signal, subject and a summary),
    where the security team's tools pick it up; "notify" mails the security
    contact and the agent's sponsor directly.  Neither keeps the request
    that raised the signal waiting on a mail server any more: mail is sent
    in the background, and the command line waits up to thirty seconds for
    it before it exits.

*** quilzo self kev FILE keeps the CVE ids of CISA's catalogue of
    vulnerabilities known to be exploited, so a flaw in this build that is
    being exploited is contained at once.  It was read from the
    vulnerability workbench's imports, which left.

*** Mail is configured in mail.json (notify/mail.json is still read).
    Shadow AI on the Fleet screen is what agents' own programs reached
    around the gateway; what people across the estate reach is in its
    proxy and DNS logs, which belong to the organisation's own security
    tooling.

** Changed

*** An agent's box narrows two more doors.
    - A tunnel through the box's proxy that carries nothing in either
      direction for five minutes is closed and recorded as such.  It
      lasted as long as the run, which may be hours.  Silence is the
      tunnel's, not one direction's, so a long download is not cut off.
    - A netlink socket opens only for the routing tables, which the C
      library reads.  Netfilter, xfrm, audit and the generic families are
      the kernel's usual way from an unprivileged socket to root, and
      nothing in a box uses them.  Packet, raw, local, Multipath TCP and
      crypto (AF_ALG, the CVE-2026-31431 route) sockets were already
      refused.

*** The admin takes you where you meant to go.
    - The name in the bar goes to the screen you start on (Today, unless
      you chose Pages), as signing in does.  It went to the page list
      whatever you started on.
    - A screen below one in the menu (a page's editor, one agent, one
      framework) has a way up to it above its heading: "← Pages".  On a
      phone, where the menu is closed, that was missing, and the screens
      that had a way up drew it three different ways.
    - A message (something refused, not saved, not found) offers the way
      back to where you were, not "Back to pages".
    - Signing in returns you to the screen you asked for, by token,
      passkey or single sign-on (OIDC and SAML).  Followed links and
      sessions that ended mid-task used to land on the start screen
      (WCAG 2.2.5).

*** Help opens in a new tab, and says so.  Help with this screen, All
    documentation and every other link that leaves the admin open in a
    new tab, so somebody partway through a form keeps what they typed.
    Each one is marked with an icon and "(opens in a new tab)" for a
    screen reader (WCAG technique G201).  The accessibility check that
    runs before publishing now reports a link that opens a new tab
    without saying so (new-tab-not-announced, advisory).

*** What waits on a person is one list wherever they look.  quilzo
    approvals now has what Approvals and Today had and it did not: the
    change to the site waiting for approvals, and the features agents
    drafted for a person to read.  All three now have the newsletter issues
    agents proposed, which were only on the Newsletter screen.

*** Scheduled publishing needs no timer.  quilzo serve publishes each
    entry within a second of its time, and at once for one that fell due
    while it was down; schedule.in_server turns it off for a deployment
    that would rather run quilzo schedule run from a timer of its own.
    The two never publish one entry twice.  The Publishing screen lists
    what fired in the last thirty days, and why anything was refused.

*** A scheduled publish goes out on the authority of the person who
    scheduled it, checked when it fires, and of whatever fires it.  It
    used to need only the timer's: somebody who scheduled a launch and then
    lost the publisher role, or whose grant ran out, still had it published
    by an administrator's cron token.  It carries the publisher role and no
    more, every gate runs then, and the record says it came by the schedule
    and names who fired it.

*** A site that has chosen no style is drawn in Material 3 Expressive, as
    the admin is.  The Design screen offered Classic as the default and
    Material 3 Expressive as one choice among seven, while the admin a
    person was looking at was drawn in it.  Expressive is now the default
    and listed first; Classic is the calmer choice, the look sites had
    before styles.  Motion's and the colour scheme's options are Springy
    and Playful, so no choice is called Expressive twice.

*** The admin on a phone and a laptop.  Every screen was opened at 1280,
    768 and 390 pixels and measured: a table on a phone is now a card for
    each row, its long names wrapping, rather than columns scrolled off the
    right of the screen; a row's actions are one button at its end, on
    Pages and Data alike, whose icon is no longer squeezed to a sliver; the
    media library is a card for each file, its rights, crop, address and
    replacement under Details; Pages names each page by its title; and the
    access table says Edit drafts and Install features rather than the
    names the policy file uses.

*** Pages has a New page box: an address, made into a page name, opens
    the editor on it.  A page could only be started by typing its name
    into the browser's address bar.  Today shows the site at a glance (its
    pages, records and files, when it was last published and by whom) and
    where to start, so a quiet day no longer reads as an empty store.

*** A page is given a form in its editor: Form on this page lists the
    forms there are, and None takes it off.  Forms said to paste HTML into
    a template; it now points at the editor, and keeps the code for a
    design of one's own under a disclosure.

** Fixed

*** The connected-accounts callback no longer reflects what it is sent.
    It turned a provider's error text into a message on the Connections
    screen.  The address is one anybody can link to before any state is
    checked, so that text could be a sentence of the linker's.  Now it
    shows only an OAuth error code (access_denied and the like).

*** Pictures from OpenAI's current image models.  Every request for a
    picture carried response_format=b64_json, which those models refuse
    as an unknown parameter, so quilzo media generate through OpenAI
    failed.  It is sent now only for a route added with --ask-base64 (or
    "Ask it for the picture itself" on the Models screen), for an
    endpoint that answers with a link unless asked; a link is still never
    fetched.  A provider's refusal of a picture (its content policy) goes
    back to whoever asked, rather than resting the route and trying the
    next.

** Added

*** The editor's preview shows what you type, before you save.  A
    moment after typing stops, the page beside the form shows what is
    typed, rendered as readers will get it and saved nowhere; without
    script, "Show in the preview" does the same.  What is typed and not
    saved is kept for the tab: after a reload the editor offers to put it
    back, and leaving with it unsaved asks first.  Showing unsaved words
    takes what saving takes, so somebody who may only read sees the saved
    draft.

*** Video from a prompt.  quilzo media film "PROMPT" --author WHO
    [--aspect 9:16] asks a gateway route added with --films, through one
    of two new provider profiles: google-veo (Veo through the Gemini
    API: predictLongRunning, then the operation asked after) or fal
    (fal's queue: taken, asked after, the video fetched from fal's
    storage without the key).  Every address an answer names must be
    https on the provider's host, a key never follows a redirect to
    another host, and what comes back must be an MP4.  It is stored
    marked as generated, with the model, the prompt and the person
    accountable, and needs captions before a page shows it, as every
    video does.  Masked, budgeted and in the ledger as pictures are.

*** Subscriptions.  A catalogue row with "interval": week, month or year
    is sold as a subscription: a recurring price at Stripe, a payment
    link that starts a subscription the buyer manages there, and a Buy
    button that says "£12.00 a month".  The demo shop's product type has
    the field.

*** Agents ask other vendors' agents, over A2A.  A new integration kind,
    a2a: quilzo integrations import agent-card.json declares one from its
    card, switched off.  Its skills are tools, pinned, allowed and
    approved as an MCP server's are; a call is message/send naming the
    skill, and a task it takes on is followed with tasks/get until it
    finishes or ninety seconds pass.  A card whose service address is off
    the declared host is refused before anything is sent, and the
    message id is the step's idempotency key.

*** The content API describes itself.  /api/v1/openapi.json is an
    OpenAPI 3.1 document of every route: pages, collections and records,
    similar pages and search by meaning, and the replica routes, with
    their parameters, their If-Match rule and their errors.  The same for
    every installation, so it needs no token; everything it describes
    still does.  A test fails on a route served and not described.

*** A remote MCP server's sign-in, discovered.  quilzo connect provider
    discover NAME https://SERVER/mcp reads the server's protected-
    resource metadata (RFC 9728) and its authorization server's (RFC
    8414 or OpenID discovery), requires PKCE with S256, registers this
    installation there as a public client (RFC 7591) or takes
    --client-id, and keeps a provider whose tokens carry the resource
    indicator (RFC 8707) and go to that server alone.  Each person then
    connects in Your accounts, and an MCP integration naming the
    connection reaches the server as them.

*** An agent's writes elsewhere are not done twice, and not lost.  Each
    write is named by its run and step and sent with an Idempotency-Key,
    the same whenever that step is sent again, so a service that keeps
    them does it once.  A busy or silent far side is tried again, up to
    three times, as Retry-After asks: a read always, a write only with
    its key.  One that still did not get through is kept, with the key
    and what came back but never the arguments, on the Integrations
    screen and in quilzo integrations undelivered, for a person to settle
    and take off the list; nothing is sent again by itself.

*** Search engines told what changed, as it is published.  With
    site.indexnow on (off by default), each publish sends the addresses
    of the pages that changed to IndexNow, which Bing, Yandex, Seznam,
    Naver and others share: a page reusing a changed block among them,
    never one left out of results or a block's own.  The key that shows
    the addresses are the site's is served at /KEY.txt.  The first
    publish after turning it on is only a starting point.  It goes out
    as its own network purpose, indexnow, refused when offline.

*** What people looked for and did not find.  The cookieless counts now
    include addresses people asked for that are not on the site (not the
    ones somebody hunting for an admin asks for, which the shield keeps)
    and searches that found nothing, masked as unanswered questions are.
    The Analytics screen lists both, each missing address with the page
    it most likely meant and, for whoever may publish, "Redirect it".
    quilzo redirect list | add FROM TO [--temporary] | remove FROM keeps
    the same redirects, and the running site reads them again within a
    second of a change: no restart.

*** Pages tell search engines and agents where they stand.  Every page
    names where it lives with rel=canonical once the site has an address
    (a layout that writes its own keeps it).  A page can be left out of
    search results ("noindex", or the editor's box), in its head, its
    X-Robots-Tag and the sitemap, or name the address it copies
    ("canonical", or "Published first at") and leave the sitemap.  Every
    page has a Markdown copy of its words at /NAME.md, linked from the
    page and from llms.txt, never indexed and naming the page as
    canonical.  "block" and "noindex" are system fields, so a typed page
    can carry them; a typed page names a canonical address through a
    "canonical" field its type declares.

*** A form can take a file: a CV, a photograph of the damage.  A field of
    kind file takes images and documents, or the kinds it names; a file
    is taken by what its bytes are, with the media library's checks (an
    image must decode, a PDF must not run anything), at most 10 MB, 20 MB
    a submission.  It is kept with the submission and deleted with it,
    and staff download it from the Forms screen as an attachment named
    afresh, under a policy that runs nothing; reading it is audited
    (form.file.read).  The starters, the embed snippet and the chatbot's
    form offers send such a form as multipart.

*** A form can ask a question only on an earlier answer: an order number
    when the topic is Returns, who gave it when "It was a gift" is
    ticked.  "when" on the field, or "Asked only when" in the form
    builder.  The server asks it only on that answer, and keeps nothing
    for it otherwise; the page hides it until then with /form/NAME.css,
    by :has() and no script, and a browser without :has() shows it with
    the words that say when it is asked.  The starter layouts give each
    field a wrapper and each choice its value, which this needs.

*** Reusable blocks: sections written once and shown on many pages.
    quilzo section block PAGE makes a page a block, quilzo section reuse
    PAGE BLOCK puts its sections on another, and quilzo section blocks
    says where each is used; the Sections screen does all three.  A block
    is saved, published and rolled back with the draft, and never served
    on its own: no address, sitemap entry, search result or chatbot
    passage.  A page reusing one is that page with those sections to
    every layout, search index and chatbot, and its ETag changes when the
    block does.  A block for members only is never shown on a public
    page, a block in a block shows nothing, and a block a page still
    reuses cannot be deleted or made a page again.

*** Visitors say whether a chatbot's answer helped.  Two buttons under
    an answer, Yes and No, counted with nothing about who pressed them;
    the chatbot's screen and quilzo assistant gaps NAME say how many
    helped.  With chatbot.gaps on, a "no" keeps the question, masked,
    beside the questions it could not answer, marked as one whose answer
    did not help; asking it again proves nothing, so such a group waits
    for a person to close it.  The form carries a token the site signed
    for that question, good once and for an hour, so the route cannot
    put somebody's own words in front of the owner.  The conversation
    page no longer says nothing is stored when chatbot.gaps keeps some.

*** A draft can be shared with somebody who has no account.  quilzo
    share PAGE [--days 7], or "Make a link" in the page's editor, gives a
    link to that page as the draft is now: later changes do not reach it,
    it lives thirty days at the most, and quilzo share revoke ID (or
    Revoke in the editor) ends it at once.  The public site serves it at
    /shared/ID with a note saying it is a draft, marked not to be
    indexed, stored or referred onward; an ended link is the site's own
    404.  Sharing takes what publishing takes, since it shows the page to
    somebody outside; revoking takes what editing it takes.

*** Backups of the whole store, encrypted and checked.  quilzo backup key
    makes a key (shown once, kept nowhere here); quilzo backup create
    FILE takes the store and the layouts, encrypted with AES-256-GCM in
    numbered parts, and reads it back whole to check every file against
    its manifest before calling it a backup.  quilzo backup verify checks
    one without restoring it, and quilzo backup restore FILE --into DIR
    restores one into an empty directory and checks its history.  With
    backup.dir set, the admin server makes one a day and keeps
    backup.keep (seven).  CP-9 is in the controls table.

*** Staff hear when an agent waits for them.  An agent's run that stops
    to ask a person to approve what it would do, or to answer it, is a
    new event, approval.waiting, in notify.events by default.  Staff are
    told by mail, or in Slack, Teams or Discord (notify.staff), which
    agent waits, never what it would do, with the admin's address;
    deciding is still done signed in, under Approvals.  No agent may
    start from it, since an agent started by another's waiting is how a
    queue feeds itself.

*** A calendar and issues, ready-made for agents acting for somebody.
    quilzo integrations template lists them, and quilzo integrations
    template google-calendar (or microsoft-calendar, github-issues)
    declares one, switched off and unpinned, using the account each
    person connects at the provider.  Each reads (events, issues) and
    adds (a meeting, an issue, a comment), and nothing more; --uses
    narrows it further.

*** Agents act with a person's own accounts.  An administrator declares
    a provider (quilzo connect provider add NAME --template google,
    microsoft or github, or its endpoints), people connect their accounts
    on Your accounts or with quilzo connect start, and an integration that
    names a connection goes with the account of the person the agent acts
    for, and nobody else's.
    - OAuth 2.1 with PKCE, and a state that finishes once, for the person
      who started it.
    - Tokens are sealed, refreshed when due, sent only to the hosts their
      provider names, and revoked at the provider on disconnecting.
    - An agent acting on its own name, and the gateway, have no person,
      and are refused.

*** A model reads text aloud.  quilzo media speak "text" (or @FILE)
    --voice V --author WHO sends it to a route that speaks (quilzo gateway
    route add NAME --speaks, or "It reads text aloud" on the Models
    screen), and stores the MP3 marked as a synthetic voice, with the
    model and the person accountable, and its words as its transcript.
    Text holding personal data goes only to a route allowed it, since a
    voice cannot leave it out the way a masked prompt does.

*** A model drafts a video's captions.  quilzo media transcribe VIDEO
    --lang en --author WHO sends the recording to a route that transcribes
    (quilzo gateway route add NAME --transcribes, or "It transcribes
    recordings" on the Models screen).  The timed text comes back as
    WebVTT and is stored as a caption file marked as a model's, unattached.
    A person reads it, mends it, and attaches it with quilzo media
    captions, so the gate that refuses a video without captions is
    satisfied by a person and not by a model.  A recording's voices cannot
    be masked, so it goes only to a route allowed personal data, and it
    is charged by the minute (--price-minute).

*** Chatbots find passages by what they mean, not only by their words.
    Add a route that makes embeddings (quilzo gateway route add NAME
    --embeds, or "It makes embeddings" on the Models screen) and name it
    in chatbot.embeddings.  Chatbots that answer with a model then find
    the passage about returns when a visitor asks about a refund.
    - The site's passages are embedded after each publish (or now, with
      quilzo assistant embed), and kept by hash, never by their text.
    - Each question is embedded as it is asked, masked.
    - A passage that shares too few of a question's words is answered
      from only when it is at least chatbot.embeddings_floor alike.
    - Chatbots that only quote their pages, or are kept to one route,
      send nothing.
    - The test screen shows each passage's meaning rank and similarity.

*** A chatbot chooses the model that answers it.  A chatbot can name a
    route, or the model a route serves, that answers it first, with the
    gateway's other routes behind it: --route on quilzo assistant add, or
    "Model that answers first" on its page.  With --route-only ("Only that
    one"), its visitors' words go to that route and no other, and when it
    cannot answer the chatbot quotes its pages instead.

*** Anthropic's models are spoken to natively.  Anthropic is in the
    providers' catalogue (quilzo gateway providers anthropic), and a route
    to it speaks its Messages API rather than an OpenAI-compatible
    imitation.  A route can say:
    - how hard the model thinks (--effort);
    - that it runs only in the US (--geo us);
    - that a request its model declines is answered by another of
      Anthropic's models (--server-fallback).
    These are on the Models screen too.  A temperature or a top_p, which
    Anthropic's models refuse, is refused where the route is made.  A
    request a model's safeguards decline goes back to whoever asked, says
    why, and is not sent on to the next route.

*** A chatbot answers from the site's records too.  An owner chooses which
    collections it reads (products, events, opening times), on the
    chatbot's page or with quilzo assistant add --collections, and it
    answers from their published records as it does from pages: each
    record a passage of its fields, cited by its name.  Fields named for a
    person (an email address, a telephone number, an address, a password)
    are never read, and the chatbot reads the records again when they are
    published anew.

*** A chatbot can ask one of the site's agents to do something for a
    visitor.  A new kind of action, agent: the chatbot offers it ("Look up
    your booking"), the visitor sees what will be asked, corrects it if
    it is wrong, and presses to ask.  The public site, which is confined,
    hands the request to the admin server, which runs the agent on its
    sponsor's authority, no more than an author's: as a reader unless the
    action says it writes, and then still only drafting and proposing.
    What the visitor typed is given to the agent as somebody else's
    words, and the run is tainted by it from its first step.  Who the
    visitor is comes only from a token the host site signs (HMAC-SHA256,
    with a key sealed as a credential the chatbot names), checked again
    by the admin server, never from what they type; an action can be
    offered only to visitors the site vouches for.  The visitor is shown
    the agent's answer when its run read nothing private and waits for
    nobody; otherwise they are told a person will look at it.  quilzo
    assistant action NAME ACTION --kind agent --target AGENT [--fields
    a,b] [--writes] [--identity --identity-secret NAME], or the
    chatbot's page in the admin.

*** An agent's own run does what the agent interface offers.  A manifest
    could declare any operation of the agent interface (records, media,
    menus, newsletters, chatbots, research, the site crew); an outside
    agent calling them through MCP got them, and the same agent run here
    got "not implemented" for all but nine.  A run now performs the reads
    and the writes that make a draft or a proposal (quilzo agent
    capabilities lists them) through that same interface, as the run's
    caller, with the role each needs and every check its handler makes; a
    write waits on the agent's autonomy except a proposal.  An
    administrator's reports, and publishing other than by proposing, are
    not an agent's, and a manifest naming one is refused when it is
    written.  So a gardener agent can read list_chatbot_gaps and draft
    the page that answers what a chatbot could not.

*** What a chatbot could not answer becomes the page that answers it.
    Off until the owner turns on chatbot.gaps: then the questions a
    chatbot could not answer are kept, with addresses, numbers and cards
    masked and nobody named, a day to a file, deleted whole after
    chatbot.gaps_days (30).  The chatbot's page in the admin, and quilzo
    assistant gaps NAME, group them by what they are about, most asked
    first, each with a link to write the page that answers it, or to
    close it as needing none.  When the site is published, each open
    group's questions are asked again (at most 25), and a group the
    chatbot now answers is closed, naming the page it answered from; one
    asked about again opens again.  With chatbot.gaps_agents on as well,
    an agent holding list_chatbot_gaps reads the open groups too, to
    draft the pages for a person to review.  The audit log says a group
    was read, checked or closed, never what was asked.

*** An agent's browser and workspace work together.  browser_save keeps
    the page in the workspace as a PDF, a picture of all of it, its markup
    or its words; browser_download presses a link that downloads and
    keeps the file (at most 32 MB, under a name of its own, never a path
    the server chose), weighed as the press it is; browser_upload puts a
    workspace file into a page's file field, only on a host the agent may
    send to and always after a person is told which file and where.  The
    first two need file_write, the third file_read, and all three keep to
    the workspace's quota and to a run that only reads.

*** Agents read the web and search it, without a browser.  web_fetch reads
    an https address: a page's words and links, without its scripts and
    forms, or text and JSON as they are.  It reads the hosts the
    declaration's "web": {"fetch": [...]} names, or any public host with
    {"any": true}, never a private address, and a redirect is held to the
    same.  web_search asks the provider agents.search names (Brave,
    Tavily, Exa, Serper, SerpApi, Kagi, Perplexity, Jina, or your own
    SearXNG at agents.search_url), with its key sealed as
    search-PROVIDER.  Both taint the run, and once a run holds something
    private an address or a query waits for a person, since either could
    carry it.  Claude Code's WebFetch and WebSearch now import as these.

*** An agent's browser does more of what Chrome does.  New actions, each a
    capability:
    - browser_scroll: by screens, or an element into view;
    - browser_key: Enter, Tab, Escape, arrows, Page Up and Down and the
      rest.  Enter or Space that would press a button or send a form is
      weighed as pressing that button is;
    - browser_navigate: back, forward or reload, never back to a page a
      form led to, which would send the form again;
    - browser_wait: until the page shows some text, or for some seconds;
    - browser_hover: rest the pointer on something, as for a menu;
    - browser_emulate: the page as a phone, tablet, laptop or desktop, in
      light or dark, with reduced motion, or as printed;
    - browser_console: what the page logged, its errors and the requests
      that failed, kept only for an agent that may read it;
    - browser_check: the page's speed (first byte, largest contentful
      paint, layout shift, requests), what the accessibility checks find,
      and what a search engine reads of it.
    Anthropic's, OpenAI's and Gemini's computer-use models now scroll,
    press keys, wait, hover and go back through these, where before they
    were refused.  A browser grant over MCP that offers every action
    offers these too; name --ops to hold one to fewer.
    quilzo browser find lists the Chrome, Chromium, Edge and Chrome for
    Testing builds this machine has, with their versions, for
    browser.path; quilzo browser status says the version in use.

*** Agents edit, move, delete and search files, and run code.  An agent's
    workspace had list, read, write and run a named program.  It now has:
    - file_edit: replace exact text (once, or every time), or put lines in
      after a line;
    - file_move: rename or move, where what was at the new name goes to
      the bin rather than being lost;
    - file_delete: into the workspace's bin, kept a week, from where
      file_move brings it back; deleting from the bin is for good and
      waits for a person;
    - file_search: files by name, lines by text or regular expression;
    - file_read of part of a file, by its lines, however large;
    - code_run: Python, JavaScript, TypeScript, shell, Ruby, Perl, PHP or
      Lua, with an interpreter the declaration names.  The code reads the
      workspace and changes nothing there unless it says "writes", and
      then a person reads it whole and approves it first.  It never
      reaches the network.
    OpenAI's, Copilot's and Bedrock's code interpreters now import as
    code_run, and Claude Code's Grep, Glob and Edit as file_search and
    file_edit.

*** Models from more than forty providers, by name.  quilzo gateway
    providers lists them, from runtimes on this machine to model makers,
    open-model hosts, routers, clouds and regional providers, each with
    what it says it does with what it is sent; quilzo gateway route add
    NAME --provider ID takes the address and the way it wants its key from
    the provider's profile.  A provider's route reaches that provider's
    host and nothing else, at public addresses (this network for a local
    runtime), and its key can be sealed (--key-secret) rather than in an
    environment variable.  The Models screen offers the same list.  An
    operator's own providers.json adds profiles or replaces built-in ones.

*** Staff hear what visitors are waiting on.  When a visitor asks a
    chatbot for a person or sends a form, notify.staff is told: by mail,
    or in a Slack, Teams or Discord channel through an incoming webhook
    whose address is kept sealed (secret:NAME); an address in the setting
    itself is refused.  A notice says which chatbot or form and how many,
    never who or what they wrote, and at most thirty go in an hour.
    notify.events chooses what is told.

*** Agents start on their own.  A trigger starts one at a time of day
    (quilzo agent trigger NAME add --every 24h --at 07:30 --zone
    Europe/London) or when something happens: pages go live, a draft is
    proposed, records change, a form is sent, a booking is made, somebody
    joins the newsletter, a visitor asks a chatbot for a person, or a
    chatbot cannot answer.  Each run is the agent's sponsor's, checked when
    it starts, and capped at drafting and proposing.  A trigger starts at
    most its day's runs, never a second while its first is going, never
    from what its own agent did, and a chain of agents starting each other
    stops at three.  The agent's page in the admin has the same, under
    When it runs on its own; agents.triggers turns it off.

*** What happens on the site is kept as events, without people in them:
    which page, collection, form or chatbot, never who or what they wrote.
    The public site keeps the events visitors cause in a log of its own,
    and one that claims a page went live is refused and recorded.

*** Spreadsheets and calendars in and out.  quilzo records import reads a
    CSV from Excel, Google Sheets, Numbers, Airtable, Notion or Shopify
    into any collection, whatever it was saved as (commas, semicolons or
    tabs; UTF-8, UTF-16 or Windows-1252), matching columns to fields by
    name, label or --map, reading each value as its field's kind, and
    asking rather than guessing when a date could be day or month first.
    A row is the record already there when its id or key field (the page
    link by default) says so, and is changed rather than added; a sheet
    brought in twice is the same records.  One bad row refuses the sheet
    unless --skip-bad; --dry-run writes nothing.  quilzo records export
    writes a collection back out as CSV or JSON, which comes back in as the
    same records, with no cell a spreadsheet would run as a formula.
    quilzo events import brings a calendar's events in from a file or
    the address Google, Outlook or Apple publish it at: each time a
    repeating event happens, a year either side, on the site's clock, with
    private events left out and the address never printed.  The Data
    screen downloads a collection as a spreadsheet and brings rows in, and
    Make's From a spreadsheet reads the same files.

*** Bring a site from the file it exported.  quilzo site import, and
    Make › Bring your site, take a WordPress or Squarespace export (.xml),
    a Ghost export (.json, with --site for where it was, for its
    pictures), or a folder or zip of a site's files: the Markdown of Hugo,
    Jekyll, Eleventy, Astro, Docusaurus or Gatsby, a Notion or Obsidian
    export, or the pages Webflow exports.  Pages keep their headings,
    lists, numbered lists, quotations, links, bold, italic and pictures;
    posts become the blog, each with its picture, author, tags, dates and
    the pictures among its words; links between them go to where they are
    now; the menu is the export's; every old address redirects.  Drafts,
    private posts and posts for members or paying subscribers stay behind
    and are counted on the card, as are comments and shortcodes.  A
    package can bring records, named by the hash of a staged file and
    written once, when it is first installed.

*** A site brought by its address keeps its links, bold, italic and
    lists: prose sections take a body in the prose subset, and a table's
    row reads as one line.

*** The prose subset takes a picture from the library, on a line of its
    own with what it shows (![Three rye loaves](/media/ID)), and backslash
    escapes (\*, \[).  A link with no words is not one, and a quotation
    looks like one.

*** A post with no summary is listed, and described to search engines,
    by the start of its words.

*** A blog: posts newest first, each on its own page, older ones a page
    at a time.  quilzo blog setup, or Ready to add on the Make screen,
    installs it as one feature: a "post" type (title, page link,
    published, written by, summary, the post, picture, tags, last
    changed), its collection, a listing of them newest first, a Blog page
    and a page for each post.  A posts section on any page shows the
    newest, with when each went out and who wrote it, and Older and Newer
    posts lead back to the first.  Each post's page has its words with
    headings, lists, bold, italic and links, its tags, the posts either
    side, and schema.org BlogPosting data (headline, dates, author,
    picture, keywords); it is an article to whoever shares it.
    site.blog names the blog's listing, and a site with no feed gets the
    blog as its feed at /feed.xml.  A package can carry a "blog" block,
    so a site an agent proposes arrives with one.

*** Events: what is on, a calendar to subscribe to, and when and where
    for search engines.  quilzo events setup, or Ready to add on the Make
    screen, installs events as one feature: an "event" type (title, date,
    time, end_date, until, venue, location, online, summary, picture,
    price, tickets, status), its collection, a listing of what is to come
    and one of what has been, a What's on page and a page for each event.
    An events section on any page lists what is still to come, with the
    date as people say it, a link to the event's page and a calendar entry
    to add; an event leaves the list when it is over, by the site's clock,
    with nothing to publish.  /events.ics is the site's events as a
    calendar visitors subscribe to (advertised in the head and llms.txt),
    and each event's page carries schema.org Event data: start and end
    with the site's offset, place or online location, status (cancelled,
    postponed, moved online) and tickets.  Records gain a time-of-day
    field, listings gain upcoming and past conditions (with a fallback
    field, so a festival stays on until its last day), and a page listing
    by the clock is never answered "not modified" with an event that is
    over.  site.events names the events listing and site.timezone the
    site's clock.  Over MCP, upcoming_events shows an agent what is on; a
    package can carry an "events" block, so a site an agent proposes
    arrives with its calendar.

*** A newsletter, with every address asking for it first.  A newsletter
    section on any page takes sign-ups; the address is mailed one link,
    and nothing else is sent until the button on the page it opens is
    pressed (a filter opening the link is not a yes).  An address that
    never does is forgotten after a week, and one address is mailed a
    link at most once an hour.  An issue is a published page: quilzo
    newsletter test PAGE --to YOU, then quilzo newsletter send PAGE
    --yes, or the Newsletter screen.  It goes as text and HTML, one
    message to each confirmed address, paced, written down as it goes and
    carried on from where it stopped, with a link to leave and the
    List-Unsubscribe and List-Unsubscribe-Post headers of RFC 8058, which
    end the subscription with one POST and delete the address.  The
    public site writes sign-ups and never reads an address back; links
    and ids are keyed hashes made with newsletter.key, which the site can
    read and not write.  Lists import from Mailchimp, Substack, Ghost,
    MailPoet, Buttondown and others (quilzo newsletter import FILE
    --agreed "where they agreed"), keeping anybody who left there as
    having left, and export as CSV.  Webhooks can be told "subscribed"
    and "unsubscribed", with no address.  Over MCP an agent sees the
    counts (newsletter_status) and proposes an issue (propose_issue); a
    person sends it.  A store a service manager keeps read-only needs
    quilzo site --prepare run once to make the newsletter's directory and
    key; until then the site starts with sign-ups off and says so, as it
    now does for bookings too.

*** Agents can ask the site (NLWeb).  The public site answers a question in
    plain words with the published pages that match, as schema.org
    objects: GET or POST /ask with query, mode (list, summarize or
    generate), query_id and decontextualized_query, streamed as
    server-sent events unless streaming=false, and the same over MCP at
    /mcp with one read-only tool, ask.  Summarize and generate add the
    answer the site's own question box gives, with its sources, limits
    and disclosure.  It answers only from what a visitor may read now,
    never a members' page, writes nothing, and sits behind the site's
    crawl licence like every page.  /llms.txt says where it is.  The
    question box no longer reads the service a booking section books or
    the board a comments section shows as something the page says.

*** Bookings: appointments on the site.  A site says its hours each week
    in its own time zone, the dates it is closed, how much notice it
    needs and how far ahead it books, and the services it offers, each
    with how long it takes and how long to leave after it (quilzo booking
    setup FILE, or the Bookings screen).  A booking section on any page
    offers the free times as buttons, with no script; a visitor books one,
    is shown it on the same page with a calendar file to add, and can
    cancel it from there.  The time is checked again under a lock as it
    is booked, so two visitors cannot get it.  One calendar, one booking
    at a time.  The public site keeps the calendar, which holds when and
    for what and never who, and writes who booked once without being able
    to read it back; names, email addresses and telephone numbers are read
    on the Bookings screen and in quilzo booking list, and forgotten a set
    number of days after the appointment.  quilzo booking feed makes a
    calendar address for the owner's phone, showing times and services and
    never names.  Webhooks can be told "booked" and "booking-cancelled",
    with the service and nothing else.  A feature package can carry a
    "bookings" block, so a site an agent proposes arrives taking bookings;
    its card says what it collects and two people approve it, an upgrade
    keeps the owner's calendar address and any pause, and a setup made by
    hand is not taken over.  Over MCP, booking_times shows an agent the
    free times visitors see, and never who booked; on the page, a visitor's
    own browser agent is offered the booking form as a WebMCP tool, which
    the visitor still submits.

*** An agent can be the site crew, and a person still decides.  Over MCP,
    site_build makes a whole site from a description, site_import brings
    one in by its address, and propose_feature takes a package the agent
    wrote.  None of them installs anything: each waits as a proposal, under
    Proposed for you on the Make screen, on Approvals and in quilzo feature
    proposals, until a person reads its card and installs it under the same
    approvals as anything they bring.  A newer proposal of the same package
    replaces the older one, at most twenty wait, and one nobody reads lapses
    after two weeks.  site_blanks and site_fill then fill in what the person
    told the agent; those pages are marked as an agent's words, and the
    pages of a package an agent wrote as a model's.  Filling in blanks now
    checks each page it changes, so a deny on a page holds against a fill as
    it does against an edit.

*** Bring your site by its address.  `quilzo site import
    https://old.example --install`, or Bring your site on the Make screen,
    reads a live site, whatever it was built with: robots.txt first, then
    the site's map of itself or its links, that host only, through the
    fetcher that refuses private addresses, at a polite pace and within a
    page budget (--pages).  Each page's content, not its header, footer or
    navigation, becomes a hero and sections; the site's own navigation
    becomes its menus; its name and theme colour become its look, refused
    when unreadable; the pictures it describes are kept in the media
    library with those descriptions, and one with none is left out rather
    than described by somebody who did not write it.  Scripts, frames and
    forms are left behind and named.  It arrives as a site package, read
    and approved as one decision, its pages marked as carried over by
    software.  A site block can now carry redirects, only to the site's own
    pages and never from one, installed into the site's own redirect map
    (redirects.json in the store) and taken out with the package, so the
    old addresses keep working.  quilzo site serves that map with any
    --redirects file laid over it, a redirect that would hide a page the
    site already has is left out, and quilzo export carries the map.

*** Payments, through the provider's own checkout.  `quilzo pay enable
    stripe`, or the Payments screen, asks a second administrator to let
    the site take money; once they agree, a Stripe payment link is made for
    every product the published catalogue sells at a price, and the site
    shows a Buy button beside a product only when a live link charges
    exactly what the page says: the same product, name, amount and
    currency.  After every publish and rollback the links follow: a new
    price gets a new link, then the old one is retired, and while the
    provider cannot be reached a changed price shows no button rather than
    the old price.  The key is a sealed secret the admin reads (`quilzo
    secret set stripe`, a restricted key is enough); the site reads only
    the links.  Quilzo never sees a card and keeps no order: `quilzo pay
    orders` and the screen read the latest from Stripe, and the audit log
    records that somebody looked, never who bought.  `quilzo pay disable`
    takes the buttons down at once and retires every link.

*** A whole site from a sentence.  `quilzo site new "a bakery in Leeds"
    --install`, or Build a whole site on the Make screen, writes a site
    package: pages and sections for the kind of site (business, product,
    restaurant, portfolio, community or event; `quilzo site kinds`), main
    and footer menus, a contact form, a question box that answers from
    the site's own pages and sends nothing anywhere, a privacy page
    written from what the forms collect, why, on what basis and for how
    long, and a look checked for contrast.  A model rewrites the words for
    the description when one is allowed, and what it answers is held to
    the builder's start: links only to the site's own pages, sections this
    program renders, forms that pass their checks, nothing that reaches
    another server.  Nothing is made up: a price, an address, opening
    hours or anybody's words that the description does not give are left
    as [[blanks]], which `quilzo site fill` or the Fill in screen writes in
    once for every page they are on.  A page with a blank in it does not
    publish (the new blanks gate).  Installed, the pages are marked for
    how they were written: by a model, which discloses, or by the builder's
    template, and filling them in makes them the person's too.

*** The only administrator installs a feature alone.  A package two
    people would approve, such as one with a contact form, installed with
    no second administrator to ask, as other changes that need two are,
    and the record says it was approved alone.

*** A package can be a whole site.  Beside its pages, menus, forms and
    chatbots, a package may declare the site itself: its name, a sentence
    about it (the new setting site.description, which quilzo site
    --description still overrides), the layouts it renders through and
    its look: one of the theme's styles, a colour the palette is
    generated from, motion and any other token, refused when the result
    is not readable.  Installed, it writes the look into the templates
    directory (--templates, the same one quilzo site and quilzo serve
    read), gives a site with no layouts the starter's, and sets the name.
    One package owns the site.  A site whose name or look somebody set by
    hand is a conflict until --take-site; what it was is kept, and
    removing the package puts it back.  The layouts stay either way.

*** Agents come in from other platforms, and go back out.
    `quilzo agent import FILE` reads OpenAI Assistants, Claude Code
    subagents and Agent SDK agents, Google ADK agent configs, Microsoft
    365 Copilot declarative agents, Open Agent Specification, Letta agent
    files, Amazon Bedrock agents (get-agent or CloudFormation), CrewAI
    agents.yaml and AutoGen AgentChat components, recognising the format
    from the file.  Each agent becomes a declaration, with a report of
    what was kept, what was changed to work here, what was left out and
    what is yours to decide; --save declares it, a team's helpers first,
    and -o DIR writes it out (with any OpenAPI document its actions came
    with) to change before `quilzo agent declare`.  An imported agent
    starts at propose, or draft if it writes.  `quilzo agent export NAME
    --to F` writes one for Claude Code, the Agent SDK, ADK, Microsoft 365
    Copilot, Agent Spec or CrewAI, and says what that format cannot hold;
    `quilzo agent formats` lists them.  The admin has both: Agents, Import
    one from another platform, and Take it to another platform on each
    agent's page.

*** A declaration carries an agent's instructions and model.  The
    instructions go to the model with every decision, after the
    operations and the rules, which still decide what can be done; the
    model is tried first on the gateway route serving it (or the route of
    that name), and the others after it.  Both are on the agent's page.

*** The audit log streams to a SIEM as it is written.  Set
    siem.stream.url (https, or http to this machine) and quilzo serve
    pushes every fifteen seconds: OCSF batches that follow each other
    exactly, each with the chain envelope, every record's Merkle leaf and
    a tree head signed with Ed25519 and ML-DSA, the request signed as an
    HTTP message (RFC 9421) over its body's digest, with a key kept for
    this.  The receiver can prove it holds every record, unchanged; the
    cursor moves only when it takes a batch, and a receiver that says it
    holds less is sent the rest again.  People stay pseudonymous unless
    siem.stream.reveal is set, and a SIEM off this network needs
    siem.stream.allow_remote; both are weaker settings, so they take two
    administrators.  `quilzo siem stream status | key | send`, and posture
    reports a stream failing for an hour.

*** Privileged changes take two administrators (NIST SP 800-53 AC-5,
    CM-5(4), AU-9(5)).  Making somebody an administrator of anything,
    issuing an administrator's token, a setting weaker than its default,
    fewer approvals for a change (review.required_approvals or
    approval.required_humans, including putting one back to a lower
    default) and moving the audit log each wait for a second
    administrator, on the command line or on the People and Settings
    screens.  They are listed on the People screen under Waiting for a
    second administrator and by `quilzo pending`; another administrator
    approves or declines (`quilzo pending approve|decline ID`, or the
    buttons), which makes the change, and whoever asked may withdraw it.
    An approved administrator's token is issued only to whoever asked,
    within a day: `quilzo pending redeem ID`.  A request waits a week.  A
    store with one administrator is not left stuck: their change is made
    as they ask and recorded as approved alone, as shield changes now are
    too.  The shield's `--now` and `playbook set`, which changed playbooks
    on the machine without a second administrator, are gone.

*** Sessions end at NIST's limits, and signing in again keeps the page.
    The admin signs out a browser nobody has used for session.idle, now an
    hour by default and held between five minutes and an hour, with no
    way to switch it off; anybody who can grant access is signed out after
    fifteen minutes at most.  A minute before, the page warns as it did;
    afterwards it offers to sign in again with the person's passkey where
    it is, so a half-written page is not lost.  Only the person whose
    page it was carries on: somebody else's passkey is refused, and is
    told to use the sign-in page.  A configuration holding session.idle
    0s, or more than an hour, loads with the default and says so.  Site
    members sign in again after thirty days, not ninety.

*** Single sign-on counts as two factors only when the identity provider
    says so.  An OIDC sign-in asks for one no older than session.max
    (max_age), for amr and auth_time by name where the provider reads the
    claims parameter, and for the acr values named with `quilzo oidc
    configure --mfa-acr`; it counts as two factors when its ID token's amr
    names mfa or its acr is one of those, and its auth_time is that recent.
    A SAML sign-in counts when the assertion says so, in the context or
    attribute its preset names; nothing is asked for, because a provider
    that cannot meet a requested context refuses the sign-in.  The Okta
    preset reads an attribute statement named amr whose value is
    session.amr.  Anything else is one factor: the session waits at
    "Confirm it's you" for the person's passkey, which a Google Workspace
    SAML sign-in always does.  A second factor is always required, so
    `quilzo saml add --require-mfa` and the Second factor checkbox are
    gone.  For Microsoft Entra, add the optional claims amr and auth_time
    to the ID token; `quilzo oidc configure` and `quilzo oidc status` say
    what each provider needs, and `quilzo oidc check` whether it reads the
    claims parameter.  Posture counts a SAML provider as covering its
    people only when it can say two factors.

*** Smart lockout.  Ten failed passkey sign-ins to one account from places
    it is not used from lock those places out of it for thirty minutes,
    doubling each time up to a day; the person's own places (the networks
    they have signed in from) keep working, so nobody can lock somebody out
    by failing in their name.  A hundred failures in a row from anywhere
    turn passkey sign-in to the account off until the person signs in with
    a single sign-on that says it used two factors, or another
    administrator unlocks it on the Sign-ins
    screen, which lists who is locked out.  Nobody unlocks themselves there,
    and somebody who administers the whole site is unlocked only by another
    who does; at the store's machine, `quilzo auth unlock NAME` is how a
    store whose only administrator is locked out gets them back, with the
    short-lived token `quilzo auth recover` issues when no other is left.  Lockouts are recorded,
    raise the account-locked sign-in signal, and the person is told how many
    failures came before their next sign-in.  The counts are kept in
    lockout.json, shared by every process and kept across restarts.  Site
    members get the same, with a recovery code as the other way back in, and
    the same answer as any failure, so a lockout tells nobody an account
    exists.  Throttling can no longer be switched off and its numbers are
    held between bounds (at most 10 free failures, a ceiling between 10 and
    100 an hour, delays between 1s and 1h, an alert by 20 failures); a
    configuration holding auth.throttle, auth.lockout.hard or a number
    outside its bounds loads without it, and quilzo serve and quilzo config
    say so.  New members' recovery codes and invitations are 120 bits.

*** Enrolment links: how somebody gets their first passkey.  `quilzo
    auth enrol NAME`, or Give a passkey enrolment link beside them on the
    People screen, makes a one-time link that adds a passkey for that
    person and does nothing else.  It works once, ends within a day
    (--for sets less), and is replaced by a newer link for the same
    person; only its hash is kept, guessing at links is throttled, none
    works during a lockdown, and giving and using one are recorded.  `quilzo
    init --admin NAME` makes a new store with its first administrator and
    prints their link, so a new store has two factors from its first day;
    an upgraded one keeps its fourteen days.  Links default to
    http://localhost:8080, because a browser refuses an IP address as the
    site a passkey belongs to; admin.base_url sets another.  `quilzo
    serve` says to open a loopback admin as localhost.

*** The demonstration comes back out.  `quilzo demo remove` says what
    taking the Marginalia demonstration out of a store would remove, and
    with --yes removes it: its pages, records, types, listings, forms,
    menus, vocabularies, claim rules, images, settings, research project,
    template and example files.  Each goes only while it is still what the
    demonstration installed and nothing that stays uses it.  A page or
    record somebody edited or added, a form holding enquiries from the
    public, a listing a page still shows, an image content or the site's
    icon still uses, the template and site name while pages are left, and
    a file somebody changed all stay, and are listed.  The removal is
    published as one commit through the same gates as `quilzo publish`; if
    publishing is refused, everything the live site reads is put back and
    running it again finishes the job.  It is refused while the draft holds
    unpublished work.  The record it works from, demo.json, is checked
    against the digest the audit log holds for it, and a changed one is
    refused.  An emptied store takes the demonstration again; the install
    refuses a store, template or examples folder already holding anything
    it would write over.  Needs publish; a model cannot do it.

*** Ideas in research projects.  `quilzo research ideas NAME --count 3`
    asks a model for hypotheses the project could test, a sceptical
    critique of each, and judges them in pairs, each pair both ways round
    so a judge that prefers whatever it read first decides nothing.
    Standings come from a Bradley-Terry fit of the matches, the same
    whatever order they were played in, and an idea is ranked once it has
    played three.  Each idea is shown beside what computations made of it
    (`research run --idea ID`) and the verdict a person records from the
    claims they produced (`--verdict ID --outcome
    supported|refuted|inconclusive --claim NAME`).  Ideas proposed after
    the data was first seen say so; an idea whose text addresses the judge
    is held, never judged; a model never writes the plan.  Judging uses the
    gateway consumer research:ideas:judge, so it can be a different model
    from the one proposing.  On the Research screen and as research_ideas
    over MCP.
*** Evolve: search over programs, against an evaluator they cannot reach.
    `quilzo evolve run --program seed.py --evaluator evaluator.py
    --input data.csv --eval-input cases.csv` searches for a better
    program: a model proposes SEARCH/REPLACE diffs to the program it is
    shown first (the seed may mark what evolves with # EVOLVE-BLOCK
    markers), the changed program runs in the agent box with no network
    and bounded time, memory, files and disk, and the evaluator scores it
    in a box of its own, receiving only the files the candidate wrote in
    output/ and writing output/metrics.json, one flat object of numbers.
    The candidate cannot read the evaluator or its cases, or write its
    metrics; a run that goes over a limit is not scored.  Selection is
    deterministic code, weights from --weight NAME=NUMBER (negative to
    minimise) or the plain mean.  Because the search adapts to its
    evaluator's cases, --test-input and --test-eval-input give a test set,
    scored once on the final population and never used to choose.  Every
    generation is journalled before the population changes, with the seed
    and model on its first line, and every program that ran is kept by its
    digest.  Programs and their output reach the model fenced as data.
    Needs use-models; costs the model gateway as consumer "evolve".
*** The catalogue for shopping agents, over UCP.  With ucp.catalogue on,
    the site serves the Universal Commerce Protocol (version 2026-08-25):
    its profile at /.well-known/ucp, and catalog search, lookup and
    product at /ucp/catalog/.  The same listing and prices as
    /catalogue.json, every product in it rather than the listing's first
    page, read fresh on each request.  Reads only: there are no carts,
    checkout or orders, and an agent buys from the product's page.  Each
    address has ucp.requests.per_hour.  The demonstration shop turns it on.

*** Agents that pay, within mandates.  A REST operation that spends money
    declares "pays": where its amount, currency and payee are, a
    parameter, body.FIELD or =VALUE.  `quilzo spend grant AGENT` gives an
    agent a mandate: a currency, named payees, a limit per payment, a
    budget and an end date, granted by a person who may approve its
    actions.  A payment within a mandate granted --without-asking goes
    ahead and is debited; one within a mandate that asks, or beyond every
    mandate, waits for a person, and a person may approve one on its own.
    Amounts are read exactly in each currency's decimal places.  Every
    payment leaves a receipt (`quilzo spend receipts`), a failed one still
    counts, and paying is never earned away from asking.  An agent's call
    may now send a JSON body to an operation that declares one.

*** Features from a repository.  A pull request's CI posts a package to
    /features/check and gets the card and the plan, with both as Markdown
    for a comment; the CI that runs on merge posts it to /features/install
    and the feature is installed, or waits for a second person in Approvals
    with the job as the one who asked.  The job holds no secret: it uses a
    token exchanged for its identity token.  The same card, plan and rules
    as the Make screen apply, and installing from another machine takes a
    token that is short or bound to a key.

*** Short-lived and bound credentials for programs.  A CI job exchanges
    the identity token its platform signs for each run for a short Quilzo
    token, so no secret is kept in the repository: `quilzo trust add
    docs-ci --github octo/docs --ref refs/heads/main --as ci:docs --role
    author --on /docs` declares which issuer, audience and claims are
    admitted and what the job gets, for at most an hour; GitLab and any
    other issuer with a discovery document work the same way.  POST
    /token/exchange is RFC 8693 token exchange: a job's identity token, or a
    Quilzo token for a narrower one that lasts fifteen minutes.  A token can
    be bound to a program's key (`quilzo token keygen`, `token issue
    --key`, or a DPoP proof at the exchange): it is then accepted only with
    a fresh proof made by that key, RFC 9449, at /mcp, /v1 and /a2a, and
    refused everywhere else.  From another machine, publishing, rolling
    back, granting, managing tokens and agents and installing need a token
    bound to a key or one that lasts an hour or less; reading and drafting
    take any token (token.proof_for_risky).  New tokens last a week by
    default and ninety days at most (token.ttl.default, token.ttl.max).

*** Ask Today a question, and keep the answer.  Somebody who may use the
    store's models asks in their own words, and the answer is cards Quilzo
    draws: a figure, a table, bars, what is waiting, agent runs.  The model
    is told the collections they may read, by their fields and choices, and
    the agents they manage, and chooses the cards and what each counts in
    JSON; it never sees a record.  Each card is checked as a listing is and
    filled in from what the person may read, and says in words what it
    counted.  "Keep on Today" pins the answer as a view, drawn again on
    every visit with the access the person has then.  Charged to their
    budget; `quilzo config set today.ask false` turns asking off.

*** A brief on Today: a few sentences on what matters most, written by the
    configured model below what is waiting.  The model never writes a
    figure: it refers to each by a placeholder and Quilzo puts in the
    figure, as a link to its records; a sentence with a number of its own,
    a link or markup is left out.  It is given counts, kinds of decision and
    agents' names, never anything somebody else wrote.  Written on a
    person's first visit of the day and again when the figures change, at
    most every half hour, through the model gateway and charged to their
    budget; it says which model wrote it and when.  An auditor is not given
    one.  `quilzo config set today.brief false` turns briefs off.

*** Today, the first screen after signing in.  What is waiting for you to
    decide, from every part of the program (the same items as Approvals),
    with questions first, then what cannot be taken back, then the oldest;
    and a count for each part of the store you can open: pages changed in
    the draft and not published, agent runs going, finished and gone wrong
    in the last day, visitors waiting for a reply, findings to triage and
    cases open.  Each count links to the records it counts, and the same
    grant check as the menu decides which are shown.  Signing in with a token, an identity provider or a passkey
    lands on it; "Start on Pages after signing in instead" keeps the old
    start, in your browser.

*** Roles for the people who work in an app.  A feature package can
    declare roles over its own collections, pages and agents: read or write
    a collection's records; read, write or publish a page; run an agent or
    approve what it waits for.  The lab sample example declares a
    technician and a manager.  The card lists them, and nobody holds one
    until somebody who may grant access gives it to them: `quilzo auth grant
    tess lab-samples/technician`, on the People screen, or by mapping an
    identity provider group to it.  A role is the bindings it stands for,
    marked with the role, so `auth explain` names it, and it is removed
    whole and never half of it by hand.  An upgrade that changes a role
    changes what its holders may do, and needs somebody who may grant
    access when anybody holds it; a version that drops a role, or removing
    the feature, takes it from everybody.  `feature show` says who holds
    each.  A model drafting a feature does not write roles.

*** Access per collection of records.  Each collection is checked as
    itself, at /@data/NAME, on the command line, the Data screen, the agent
    interface (list_collections, list_records, write_record) and the content
    API, so `quilzo auth grant tech author --on /@data/samples` lets
    somebody, or an agent, work on the samples and nothing else, and see
    only the collections they may read.  A role over the site covers every
    collection as before.  The content API asked about /NAME, the namespace
    of pages, so a grant on a section of pages reached the collection of the
    same name; it no longer does.

*** Permissions for working with agents.  Five decisions that were each
    given to the nearest role, and all but two meant "administrator of
    everything", are permissions of their own: run-agents, approve,
    manage-agents, use-models and install-features.  With none granted, each
    comes with the role it always did.  Each can also be granted alone on
    part of the store (/@agents for every agent, /@agents/NAME for one), on
    the People screen or with `quilzo auth grant mo approve --on
    /@agents/newsletter`; it ends, can be denied, and `auth explain` names
    it.  Approval follows ownership: an agent's sponsor, and whoever holds
    approve on the agent or on the page its action would change, decide what
    it waits for, see those runs on Approvals and open and decide them
    without the rest of the agents studio.  Naming a sponsor stays an
    administrator's, and a package that declares agents or webhooks needs
    managing those agents, or an administrator, besides install-features.
    A token caps a permission granted alone by its path and limits, so a
    reader's own token can use what they were granted; identity provider
    groups named after a permission grant it.

*** Research projects whose every claim can be checked.  `quilzo research`
    keeps a question, a plan registered before the data is analysed (its
    SHA-256 signed with the log's keys; later changes are amendments with a
    reason, made by a person), and computations: a Python, R or Julia script
    run in a box with no network on copies of the inputs named, recorded by
    the digests of its code, inputs, outputs and environment, with the
    numbers it wrote to values.json.  `research rerun` repeats one and says
    whether every number and file came out the same.  A report states a
    number only as {{claim}}, pinned to a computation, and cites only works
    an open index returned (OpenAlex, Crossref, arXiv, by DOI, PubMed ID or
    arXiv ID); `research check` flags every number written in the prose,
    every unresolved citation, every cited sentence its source does not
    support, and every claim that did not reproduce.  `research report`
    writes it with references and a provenance table.  Agents work on a
    project through research_* operations and cannot register or amend a
    plan.  The Research screen shows the report with each number linked to
    its computation.

*** A model gateway for agents built anywhere.  With models.remote on, the
    admin answers OpenAI's chat completions and responses and Anthropic's
    messages at /v1, through the model gateway: an agent made with ADK,
    Agent Framework, LangChain, CrewAI, OpenAI's Agents SDK or either
    provider's SDK changes its base address and uses a Quilzo token as its
    key.  Each call is charged to the token's person against their budget
    (door:NAME, or agent:NAME, and person:NAME), masked like every other,
    cleaned of invisible characters, and recorded as model.door, refusals
    included; it takes a token that may draft, in a store with an access
    policy.  Naming agent/NAME as the model brings that agent's memory: what
    it keeps about the person is recalled into the conversation, and the
    model is offered quilzo_remember, which Quilzo performs before answering.
    What an agent built elsewhere remembers is kept as unreviewed, and held
    until a person confirms it when the conversation carried a tool's
    results or a picture.  The Models screen shows the addresses; the
    shield can close it (models).

*** Agents learn from their mistakes.  `quilzo agent lessons reflect RUN`
    has a model write a candidate lesson ("when X, do Y") from a run that
    went wrong; `lessons try NAME ID` runs the agent's evaluations with its
    playbook and with the lesson added, under the model its runs use; a
    person adopts it for that model only if it helped and planted
    instructions steered no more cases.  Runs then read their adopted
    lessons as advice, and each run's record names them.  A lesson naming a
    host, an address or something shaped like a credential is refused, and
    one written from a run that read somebody else's words is adopted only
    by somebody who says they read it.  Agents › Runs › What it is learning
    shows the agent week by week and its lessons.

*** REST APIs as tools.  `quilzo integrations import openapi.json --uses
    A,B` declares an API's named operations as an integration of kind http,
    switched off: each operation is a tool with the same allow-list, pins,
    gateway, approvals and audit as an MCP server's.  A call is built only
    from what the operation declares, refused if it carries anything else,
    sent over https to the declared host with the credential added (bearer,
    or a named header), and its answer cut off at 32 KiB.  A REST API can be
    offered to other agents through /mcp/gateway/NAME.  The same command
    reads an MCP registry server.json into an MCP server declaration;
    without --uses it lists what is on offer and declares nothing.
*** Facts with time.  An agent remembers a fact with a key ("shipping_city"),
    and a newer fact with the same key replaces the older one, which is
    kept with when it stopped being true and what replaced it; `quilzo
    memory history AGENT KEY` shows the chain.  A fact learnt after reading
    somebody else's words replaces nothing until a person confirms it.
    Recall ranks by words and vectors fused by reciprocal rank, with
    recency, over the text itself, so it works the same whichever model
    an agent uses; it returns only what matches and stops at 6,000
    characters.

*** Features as packages (/make, `quilzo feature`): one file declares the
    types, collections, listings, forms, pages, menus, chatbots, agents and
    webhooks a piece of work needs.  `feature check` shows its permission
    card, worked out from the package: what it stores and whether that is
    about people, what it collects, shows and sends, and what its agents
    may do.  One that holds personal data, collects from the public, sends
    outside or adds an agent that acts without asking waits for a second
    administrator.  A feature only changes what it made, never publishes,
    and can be rolled back or removed as one.  `feature export --sign`
    signs it with the log's keys; `feature install --keys` checks them.
    `feature capture` packages blocks built by hand.

*** Make a feature from a sentence or a spreadsheet.  `quilzo feature draft
    "…"` (and Make › Describe it) has a model draft a package; the reader
    keeps content blocks only, never an agent, a webhook or a link to
    another site, and a draft that fails the checks is shown its failures
    once.  `quilzo feature from FILE.csv` (and Make › From a spreadsheet)
    infers a type from the values, with a collection, a listing and a page;
    `feature install --rows` imports the rows with it, after approval when
    it needs a second person.  Neither installs anything by itself.

*** Approvals (/approvals, `quilzo approvals`) lists everything waiting for
    the person looking: what agent runs ask, questions they need answered,
    actions the reviewer stopped, tool calls held for a second person, acts
    a case waits to have approved, a change waiting for review, memories
    held until confirmed.  Each says what deciding commits to.  Several runs
    waiting on the same action that only reads or acts can be let through
    together; anything that writes, sends, pays, deletes or publishes, and
    every question, is decided on its own.

*** A decider less sure than its declaration demands stops the run with its
    question, typed as it was asked, the model's guess and confidence beside
    it.  The run goes on with the person's answer (`quilzo agent answer RUN
    STEP NAME=VALUE`, or the run's page).  The reviewer's refusal is a hold
    on the exact action, and letting it through performs that action.
    Steps a decider answered show how sure it was.

*** Every step records what it cost in tokens and money; `quilzo agent cost
    --by agent|person|goal|day` and /agents/cost group the kept runs.  A run
    that paused for a person no longer starts its money budget again.

*** `earn_after` lets a kind of action stop asking after that many
    approvals in a row while evaluations still earn the agent its autonomy;
    a decline, the reviewer, the breaker or a regressed evaluation sends it
    back.  Sending, paying, deleting, publishing and signing in always ask.
    `quilzo agent earned NAME [--reset]` and the agent's page show it.

*** `quilzo agent credential issue|list|revoke|verify|keys`: a signed W3C
    Verifiable Credential another vendor's agent presents at a browser
    grant, bound to its own key and proved on every request; the issuer's
    keys are at /.well-known/agent-credentials.json.

*** `quilzo --json eval run` prints the whole report as its machine
    document, attack-success numbers and all — the planted-instruction
    rates at one and many attempts, per case — for a deployment to publish
    what it measured.

*** `agent.reviewer` (off by default) names a second model every action is
    offered to before it runs.  It sees the goal a person set and the
    action chosen, and nothing of what the agent saw — no page, no
    document — so nothing a page planted can reach it.  It can let an
    action through or stop the run with its reason, for a person to read
    and continue from; it can never widen or rewrite one.  A reviewer that
    cannot be reached fails closed: nothing runs.  It is the last defence,
    not the only one — the manifest, the breaker and ask-first are
    deterministic and come first — and it costs one more model call per
    action, which is why it waits for a setting.

*** Desktop cells, experimental: `internal/cell` drives a microVM
    (Firecracker or Cloud Hypervisor, whichever binary the operator
    installed) over its own Unix socket, restores a warm snapshot of a
    desktop, and speaks to a helper inside through the snapshot's vsock:
    the accessibility tree as an outline with references, presses, typing
    into fields, the four named keys, and a picture.  A press is weighed
    by this program from the element's role and name, Enter always asks,
    and typing into a cell is under the exfiltration breaker.  `quilzo cell
    status` says whether this machine can run one and what it would
    restore (`cell.backend`, `cell.binary`, `cell.snapshot`, `cell.memory`,
    `cell.vsock`).  One cell runs at a time.  The image, with its X server,
    accessibility tree, apps and the helper, is the operator's to build,
    and the microVM runs as this program's user without Firecracker's
    jailer.

*** The governed browser is offered over MCP, to other vendors' agents:
    `quilzo browser grant NAME --read HOST,… [--write HOST,…] [--daily N]
    [--pictures]` declares what an outside client — Claude Code, Codex,
    anything that speaks MCP — may do at `/mcp/browser/NAME` on the admin,
    reached with any token whose access reaches that path: a reader's to
    read the page, an author's to act on it.  Each call is a step of a
    kept run: the same host rule on every request the page makes, the same
    breaker, and a press that commits to something held for a person
    exactly as a gateway tool is — approved once, within the hour, by
    somebody else.  The browser is Quilzo's, in a box; the client never
    holds its pipe, and nothing it sends is a selector or a script — it
    names elements by the references of the outline it is given.  A grant
    offers no credentials and no signing in.  `--off` withdraws one.

*** A run waiting on a person over its browser can be taken over: on the
    run's page, "Take the page over and do it yourself" puts the person's
    own hands on the page — a click on the picture presses there, and text
    or Enter, Tab, Esc and Backspace go to where the page holds its focus.
    Each thing done is recorded in the log as that person's (`agent.person`),
    never as the agent's or the model's, and what a person typed is kept by
    length and digest and not by its words.  The page's rule still holds
    for their hands: the browser reaches only what the agent declared.
    The question they took it over for still wants its answer, so the run
    is declined or let go ahead when they are done.

*** `browser.cache` (off by default) replays a browser flow that worked
    without a model call.  A remembered step passes every check a decided
    one does — the manifest, the breaker, ask-first with the person — and
    confirms itself by the page that follows it: the moment the page is not
    the one the flow remembers, the model decides again and the memory is
    dropped.  Signing in is never remembered.  What the cache holds is
    sealed with the store's keyring and kept per person, so a run for one
    person never types what a run for another typed, and each replay is
    written to the log
    (`agent.cache`) with the digest of the page it ran against, so a
    receipt can say which steps ran without a model.

*** An agent's manifest may name a `dialect`: the vendor computer-use
    language its model answers in — `anthropic-computer`,
    `anthropic-browser`, `openai-computer` or `gemini-computer`.  The model
    is prompted in its own maker's shape and its answer is translated onto
    Quilzo's actions, which changes nothing about what it may do: a word
    the manifest does not hold is refused in the manifest's own terms, and
    a vendor gesture with no honest equivalent (a scroll, a right click, a
    drag) is refused rather than approximated.  A click by coordinate
    resolves to the outlined element at that point and is weighed as that
    element's own press; typing goes to the field last pressed, re-checked
    before anything is typed.  A model answering in a dialect may return
    several actions at once, as its maker's runtime taught it; they run in
    order, one a step, and the first failure drops the rest.

*** `quilzo agent declare FILE` amends an agent that is already declared.
    An amendment that widens what it may do is refused until `--widen`,
    with the exact list: capabilities, hosts, programs, tools, sign-ins,
    what stopped asking a person, budgets, what it reads and remembers,
    autonomy and kind.  The widening allowed is written to the log as it
    was read.

*** `quilzo agent receipt RUN --bundle FILE` writes one HTML file of the
    run's steps, the pictures a person was asked about, and the receipt
    with its proofs.  It needs the access the agents screen needs, since it
    carries the run's pictures, and nothing in it runs.

*** `quilzo agent template save FILE` adds an archetype of the store's
    own, a kind with a starting declaration, and `quilzo agent new NAME
    --kind KIND` knows it.  A template validates as written, never claims
    a built-in kind or the supervisor's, and is not removed while agents
    of its kind are declared.

*** An agent holding `decide` asks a declared decider a typed question
    inside its run.  An answer under the decider's confidence threshold
    says a person decides and carries no value to act on.

*** `quilzo desktop consent` asks the desktop's own portal for the
    person's keyboard and pointer and gives the session straight back.
    No run may use a person's desktop.

** Fixed

*** Pictures and the writing assistant go through the gateway.  quilzo
    media generate and the assistant (quilzo assist and the Assistant
    screen) called the configured model directly: outside every budget
    and the usage ledger, and media generate without taking personal data
    or credentials out of its prompt.  A route added with --draws is an
    images model used for pictures and nothing else, charged by the
    picture (--price-picture); a picture is charged to media and to the
    person, and the assistant to assist and to the person, each against
    its budget and on the record.

*** A --token given on the command line is who every command acts as.
    The check in front of each command used it, and a hundred and
    thirty-five places inside commands then acted as, and recorded,
    whoever the token file or QUILZO_TOKEN named instead: an approval that
    needed a second person could compare the wrong one.

*** An entry added to the schedule while another was cancelled or fired
    is kept.  The browser, the command line and the timer each read the
    schedule, changed it and wrote it back with nothing in between, so the
    second writer lost the first's change.  They now take one lock.  And
    quilzo schedule add and cancel record the person --token named, not
    whoever QUILZO_TOKEN or the token file did.

*** A forwarded address is believed only from a proxy that is named or one
    on this machine, and the shield exempts this machine, not every private
    address.  With site.trusted_proxy or admin.trusted_proxy on and no proxy
    named, X-Forwarded-For was believed from any private network, container
    bridge, link-local or carrier-grade NAT address, so anything there could
    choose its own address for every limit and the audit log; now only
    loopback is, and the site and the admin say once which other machine
    forwarded an address that was not believed, with the command that names
    it.  The shield never blocked or slowed an address on a private network
    or carrier-grade NAT, or a request a named proxy made itself; both are
    now counted, slowed and blocked like anybody's, and only this machine
    and the networks added with `quilzo shield trust add` are never blocked.
    `quilzo shield lift` on this machine ends a block.

*** A page of nothing but unclosed brackets or links rendered in time
    that grew with the square of its length: twenty thousand took a second
    and a half, on every request.  Each marker now looks a bounded way for
    its partner.

*** Every record has its page, not only those among a listing's first
    rows.  A detail page found its record among the rows the listing shows
    at once, so the 51st event, the 21st product of a catalogue listing
    twenty, and every post but the newest answered "not found" at their
    own addresses, linked from feeds and search results; a copy of the
    site left their pages out too.  The listing's conditions and allow-list
    decide, as before, and its number of rows does not.

*** The sitemap lists the page of every record: each post, event and
    product at its own address.  It listed pages only, so it named none
    of them and named instead the page that stands for them all, which is
    no address of its own.  A sitemap whose records depend on the clock
    changes its validator with it.

*** quilzo events setup and the Make screen's ready-made features put
    pages in the draft marked as written by Quilzo, so they publish.  They
    had no provenance, and publishing refused them until somebody said who
    wrote a page they had not written.

*** A picture field on a record's form offers the library's pictures by
    what they show: type "rye" and choose, instead of pasting the hash of
    a file.  The help under a picture's description names the picture by
    its label ("Picture"), not by its field's key.

*** quilzo records add takes a list as JSON: tags='["rye","bread"]'.
    It was stored as the words, and a type wanting a list refused it.

*** The Make screen says a ready-made feature is installed instead of
    offering to add it again.

*** quilzo mcp answers in the revision the client asked for.  On
    standard input every client was answered in 2025-06-18, even one
    asking for 2025-11-25; it negotiates as the HTTP transport does.

*** The Sign-ins screen's Help opens the manual's Sign-ins guide, not
    the automations guide that left with them.  Its first line no
    longer says "the 0 networks you declared" on a new store.  The agent
    form no longer lists security operations nothing offers, and quilzo
    help says what shadow AI is now: what agents' own programs reached
    around the gateway.

*** The public site is confined.  quilzo site re-executes itself under
    Landlock before it opens anything: it reads the store and writes only
    its own state, and the store's policy, tokens and configuration, and
    its root, are out of its reach.  It checks that at start and refuses
    to run if they are not.  What it writes moved out of the store's root
    into directories of its own (shield/, fediverse/, usage/,
    webhook-deliveries/, tmp/; webhook deliveries are kept apart from the
    endpoints and their secrets), and the first command run after an
    upgrade moves an existing store's across.  quilzo site --prepare
    makes and lists those paths for a service manager; --confine=false
    runs the site unconfined and says so.  The systemd unit in deploy/
    named the wrong store and only the submissions directory, so a site
    run by it could keep no shield state, audit record, analytics, board
    or member; it lists every path the site writes now.

*** People are pseudonymous in every part of an audit record.  The log
    pseudonymised a record's principal and the details that name a person,
    and left the same people in clear in its sentences and its resource:
    "make lee admin on /", "lee declined this", "rae is reader on /",
    "dana is the only administrator", a grant's resource "/lee".  Those
    went to the log and to every batch streamed from it.  A record's
    writer now names everybody it mentions as a person (a grant's
    subject, whom a token is for, who decided an agent's step or a held
    call, an agent's sponsor, who gave an enrolment link, the people a
    posture finding lists), and a keyed log gives them the same handle
    wherever the record names them.  The Logs screen names the people the
    store knows there too.  The test suites check every record they write
    for a person in clear.

*** Mapping an identity provider's group to admin takes two
    administrators.  A group mapped to admin, or to the analyst or
    compliance job, makes everybody the identity provider puts in it an
    administrator, and one administrator could map it from the
    Provisioning screen or quilzo scim map, past the second administrator
    that making one by hand waits for.  It waits now, on People and in
    quilzo pending, and is made when another administrator approves it.
    A group mapped to anything less is still one administrator's to map.

*** A quick answer to a running agent's question is not lost.  The run's
    page and quilzo agent approve can answer from the moment the question
    is offered, and the process holding the run removed whatever answer it
    found when it started to look, so an answer given in that moment was
    thrown away and the run waited until its time ran out.  Each question
    is stamped when it is asked, an answer names the question it is for,
    and nothing is removed before it is read.

*** Smart lockout works for IPv6.  The admin hands the lockout a client's
    source as internal/clientip writes it, which for IPv6 is the client's
    /64 as a prefix, and the lockout read that as no address at all: an
    IPv6 guesser was never locked out of anything, an IPv6 person never
    had a familiar place, and a guesser could fail a hundred times in a
    row and turn an account's passkeys off for its owner.  Every source is
    placed now, and one that cannot be is treated as unfamiliar.

*** A passkey sign-in or removal lands on the key it is for.  The admin
    changed its list of passkeys by position, with nothing to stop two
    requests at once: a sign-in wrote the counter that detects a cloned key
    back at the position it found before checking the signature, and a
    removal cut at a position found before another removal moved the
    list, so either could land on somebody else's passkey, and a save could
    write an older list over a newer one.  Every change is now made by the
    key's id, to the list as it stands, and saved before the next.

*** A budget for a model cannot be refilled by a full disk.  The gateway
    wrote each call to model-usage.jsonl and threw the write's error away,
    so a ledger that could not be written kept counting in memory while
    the file, which budgets are counted from after a restart, fell behind.
    A call the file refuses is now kept and written first at the next
    call; until then a caller with a budget is refused (one without has
    nothing to forget), and the audit log says when it started
    (gateway.ledger-failed) and when it ended (gateway.ledger-restored).
    It is not reported to the shield as overspending.

*** The audit stream's receiver checks the address its sender signed.
    Behind a proxy that ends TLS it rebuilt the address from the request,
    plain HTTP to an internal host, and refused every batch.  Its Origin
    is the receiver's public address, which a receiver of Quilzo's stream
    sets from admin.base_url.

*** Publishing over MCP is a tool of its own, quilzo_publish.  It was an
    operation of quilzo_write, whose description says "Never publishes",
    so a host that asked a person whether an agent might change the draft
    was asking whether it might put the draft in front of readers too.
    quilzo_publish tells a host it changes what readers see
    (destructiveHint), so a host can let an agent draft and still ask
    before it publishes.  quilzo_write refuses publish and says which tool
    to use; a program that called it by name moves to quilzo_publish, and
    an agent that finds its operations with quilzo_find is told.

*** --json answers in JSON everywhere it lists something.  Nineteen
    commands printed their prose instead: agent list and templates, apps
    list, audit, auth list and roles, brand check and list, browser
    status, diff, inbox list, integrations list, log, peer list, research
    list, rights, secret list, token list and version.  Each now writes
    one document (never a token's hash or a secret's value, and a refusal
    still exits non-zero), and a test runs every one of them as a caller
    would.

*** Getting started's "Publish it" opens Review.  It linked to /publish,
    which only takes the publish form, so the one link on the first screen
    a new installation shows that matters most answered "POST only".

*** Passkeys and Inventory fit a phone.  The passkey list was the one
    table in the admin outside a scrolling frame, so its Remove buttons sat
    off the right edge and the whole screen scrolled sideways; a sentence
    naming a file by its whole path did the same on Inventory.  A test now
    fails on a table outside the frame.

*** A picture opened on its own is shown as a picture.  The admin's
    style policy refused the styles the browser gives an image or a film
    it displays by itself, with an error in the console for each, so the
    picture sat in the corner of a white page.  Media shown in place now
    has a policy of its own: the browser's styles, and nothing that runs.

*** CodeQL reads this program's escaping.  Its cross-site scripting rule
    reported every page the public site renders, and its cookie rule every
    cookie the admin sets, and the required check failed any change large
    enough to touch them.  A model pack in .github/codeql/extensions says
    which functions escape and why, each claim recorded with what would
    make it wrong in docs/codeql-dismissals.md, and three small changes let
    the analyser follow the rest; twenty-one of twenty-two alerts are gone
    without a dismissal.

*** A fresh install gets its first token again.  `quilzo auth grant you
    admin` then `quilzo token issue laptop --principal you`, the way the
    manual says, waited for a second administrator nobody could be: the
    first token in a store, for the only administrator the policy names,
    is now that administrator's change, made alone, and recorded as the
    bootstrap (dual.approved, bootstrap true).  A second token still
    needs one presented.

*** What the admin does is in the audit log.  Its records took their
    principal from the detail's "by", and publishing from the browser,
    adding and resolving a note, editing a picture, exchanging a token and
    enforcing the organisation's policy were written without one, so the
    log refused each of them (AU-3: who) and kept nothing; so were the
    refusals at the door, with nobody signed in: a wrong enrolment link, a
    refused single sign-on, an account locked by somebody's guesses.  Each
    now names who acted, a refusal at the door is kept as an anonymous,
    unverified caller, and a refusal or a failure is recorded as one
    rather than as a success.  A test walks the admin's source and fails
    on a record that says nobody.

*** A grant says what it lets somebody do.  The line after `quilzo auth
    grant` was worked out from the policy as it was before the grant, so
    it said "can now: nothing" for every grant.

*** A sandboxed program can no longer reach the network through
    Multipath TCP.  Landlock's TCP rules do not apply to MPTCP sockets
    (seen on Linux 6.18), and Go has opened its listeners as MPTCP by
    default since 1.24, so an extension whose network was denied could
    still connect anywhere and listen, and an agent's program could
    listen inside its box.  The system call filter now says MPTCP does
    not exist, which Go and the C libraries fall back to plain TCP on,
    and refuses listen(2), which binds a socket that has no address
    without asking Landlock.  Extensions, which are not hardened, now
    get this part of the filter whenever their network is denied.

*** Two factors on every request a person makes to the admin (NIST SP
    800-63B-4 AAL2), checked on each request rather than at the sign-in
    form.  A session now records how it was signed in to, its assurance
    level and when.  A passkey, which asks for its PIN, fingerprint or
    face, is two factors; a single sign-on is two when the identity
    provider says it used two; a token is one.
    Signing in by pasting a token gives a session that reaches only
    "Confirm it's you" until the person's passkey confirms it.  A token
    sent in an Authorization header is refused by the admin's screens;
    programs use the command line, the API or the agent interface.  A
    passkey is added from a two-factor session.  Single sign-on required
    now holds on every request, so a session that did not come through
    the identity provider is refused however it was made.  An
    installation gets fourteen days from the first start of this version:
    until then a token alone still works, every page says when that ends,
    and the posture check mfa.no-second-factor names who has no passkey
    or single sign-on that can say it used two factors.

*** The provisioning token ends.  Whoever holds it can create people and,
    through a mapped group, make them administrators, and it worked for
    ever.  `quilzo scim token` now makes one that lasts 180 days (--days,
    at most 365); `quilzo scim status`, the Provisioning screen and the
    posture check scim.token-expiring say when it ends, a month ahead.
    An ended token is refused, which also stops the identity provider
    taking access away, so make the next one before then.  A token made
    before this release keeps working for 180 days from when it was made,
    and for at least 30 days from the first time it is used after the
    upgrade.

*** Single sign-on through OpenID Connect finishes only in the browser
    that started it.  The callback address of a sign-in somebody else
    began at the provider, opened in another browser, signed that browser
    in as them (login CSRF).  Starting a sign-in now sets a cookie the
    callback requires, as SAML sign-in already did.

*** A member's recovery code opens the account to add a new passkey and
    nothing else, for fifteen minutes.  It used to give an ordinary
    session: the members' pages, posting, new recovery codes, removing
    passkeys and deleting the account.  Adding the passkey ends that
    session and signs the member in as a passkey sign-in would.

*** A REST integration's request body is checked against the schema its
    declaration gives before it is sent.  A field the schema does not
    list is refused unless the schema allows other properties, so a model
    steered by something it read cannot add one the API would accept.

*** The fediverse inbox checks the body against the digest the signature
    covered, and only that one.  A sender that signed only the older
    Digest header could have its body replaced by anybody on the way who
    added a Content-Digest of their own, because the unsigned header was
    the one read.

*** "Confirm it's you" no longer sends a code by email.  NIST SP
    800-63B-4 forbids email for this (3.1.3.1: email shall not be used
    for out-of-band authentication), and whoever can read a person's mail
    could pass it.  A session the automations hold back is confirmed with
    the person's passkey, by signing in again through single sign-on, or
    by another administrator from Sign-ins.  Three failed passkey
    confirmations from such a session raise verification-failing for the
    automations, as three wrong codes did.

*** Agents do nothing while their actions cannot be recorded.  With the
    log writer down, the audit key unreadable, the log's disk nearly full
    or the last record refused, an agent's run used to go on acting with
    no record of what it did.  Each action is now checked against the log
    first and refused while it could not be written, and the run stops
    with that reason; a step whose record was refused stops the run
    before its next action.  The same holds for changes made over the
    machine interface, presses in a browser an outside agent drives, and
    tools forwarded to another system through the MCP gateway; reading
    goes on.  What people do in the admin and on the command line still
    goes ahead with the failure reported, as before.

*** Exchanging a copied token no longer gets round proof of possession.
    From another machine, publishing, rolling back, granting, managing
    tokens and agents and installing take a token bound to a key or one
    that lasts an hour or less.  A long-lived bearer token exchanged at
    /token/exchange gave a fifteen-minute session that passed as short,
    and an exchange proved with any key gave one that passed as bound,
    so whoever copied the token could do all of it.  A session now counts
    as the token it was exchanged from: from an unbound token it is as
    long-lived and as unbound as that token, whatever key proved the
    exchange; from a bound token, whose own key must prove the exchange,
    it is short and bound.  An app's consented token, a CI job's own
    token (quilzo trust) and key-bound tokens work as before.

*** The site's search finds pages published while it runs.  The index
    was built once, when `quilzo site` started, so a page published
    afterwards could not be found until a restart.  It is now built again
    from the live commit the first time somebody searches after a
    publish, as are the vectors behind /api/v1/similar and
    /api/v1/search/vector.

*** A run waiting for a person is never pruned.  Runs are kept up to
    five hundred, oldest removed first, and that count took away runs
    still waiting on a question, and runs a process was working on.  Only
    finished runs are removed now.

*** `quilzo --json a11y` answers in JSON: the pages checked, every
    finding, and what was and was not checked on each page.

*** `quilzo publish` with nothing drafted names the add command as it is
    typed: quilzo add NAME=FILE.json.

*** Writes that happen at the same time no longer undo each other's
    security decisions.  A sign-in in the admin could write back a token
    that the command line had just revoked; a publish sending webhooks
    could put back an endpoint removed while it was sending; provisioning
    requests arriving together, or the admin and the command line, could
    each lose the other's grant or revocation; a run could bring back a
    person's memory that had just been forgotten.  The policy, the agents'
    declarations, the webhooks and each agent's memory are now changed
    under the file's own lock on what is stored at that moment, and the
    tokens are written merged with what is stored, so a revocation is
    never lost.

*** Provisioning that cannot take somebody's access away says so.  A
    deactivation, a deletion, a rename or a group change whose access
    could not be removed answered success, and the person kept it; it now
    answers an error the identity provider sends again, and a deleted
    user or group stays until it can be removed properly.

*** Model calls work with 2026's frontier models.  Every call sent a
    temperature of 0.2, which Claude's current models answer with an
    error, so each call through such a route failed, rested the route and
    moved to the next one without anybody being told why.  Nothing beyond
    the conversation and the caller's own limit is sent now unless the
    route asks for it: `quilzo gateway route add` takes --temperature,
    --top-p, --max-tokens-field max_completion_tokens (for OpenAI's
    reasoning models), --timeout for a model that reasons at length, and
    --auto-tool-choice for a model that refuses a forced tool choice.  The
    Models screen has the same fields under "What this model is sent", and
    the list of routes and `quilzo gateway status` show them.

*** A request every route would refuse is no longer sent to each in
    turn, and no longer rests a route.  A provider's 400, 413 or 422 goes
    back to the caller, and the model door answers it with that status
    rather than 502; a timeout, a rate limit, an outage, a refused key or
    an account out of credit still rests the route and moves on, and a
    caller giving up rests nothing.  Somebody allowed at the model door
    could otherwise keep a route resting for everyone with requests it
    refuses anyway.  These calls count against their callers' budgets.

*** The reviewer is a second model.  agent.reviewer names a route, and
    the review used to walk the whole fallback chain from the first route,
    usually the model the agent itself runs on.  It is now asked through
    the route named and no other, so a reviewer that cannot be reached
    stops the run instead of being replaced by the agent's own model.
    `quilzo config set agent.reviewer` and the Settings screen refuse a
    route that is not declared, and the posture check
    agent.reviewer-not-second reports a reviewer on the first route or on
    one that has been removed.  Reviews are charged as reviewer:AGENT and
    to the person who started the run.  The action the reviewer is shown
    is fenced with a marker new for every review, so text the agent chose
    cannot close the fence and go on as the reviewer's own prompt; and
    settings that cannot be read stop a run instead of letting it run
    without its reviewer.

*** A tool choice naming one tool is passed on as that tool.  The model
    door read OpenAI's {"type":"function","function":{"name":...}},
    the Responses API's {"type":"function","name":...} and Anthropic's
    {"type":"tool","name":...} as "any tool", so the model could call one
    the caller had not asked for.  A choice of a tool the request does not
    offer is refused.

*** Drafts, features, evolution, research ideas and reflections are
    charged to the person who asked for them, as person:NAME, so a budget
    set on a person bounds those as well as agent runs.  A budget may name
    a caller by the email address single sign-on gives them
    (door:dana@quilzo.io).

*** Quilzo reads its own audit log on the server's timer.  The rules about
    Quilzo itself, what agents did and the correlations across them ran
    only when somebody ran `quilzo detect run`, a feed pushed events or a
    scheduled collection brought some in, so an installation that collects
    from nowhere ran none of them.  Every quarter of an hour in which the
    log has grown, the rules run, and what they raise goes to the
    automations.

*** A new store starts with the shipped detection rules, and a server
    whose store has no rules directory installs them when it starts.  They
    are files in the store as before, the organisation's to tune or switch
    off; a store that has its own rules keeps exactly those.

*** Making somebody an administrator is detected on every path: the
    People screen, `quilzo auth grant`, and provisioning from an identity
    provider, which now records each role it gives and takes away
    (scim.granted, scim.revoked).

*** `quilzo token exchange --json` is recorded; the session a script asked
    for left no record.

*** A webhook a feature package adds, changes or removes is recorded on
    its own, with where it sends, and feature.installed records how risky
    its card said the install was and how many people it took.

*** A deny or a grant on part of the site holds on the machine interface.
    Its page operations asked about the whole site, so somebody denied
    /legal could list, read, search, compare, write and export it over MCP,
    and somebody granted only /blog could use none of it.  Each page is
    now asked about its own path.

*** Publishing asks about every page it would change, on the command
    line, in the admin and over MCP.  A deny of publishing on one page was
    not seen by the check on the whole site, so publishing the draft
    published that page too.

*** An export is refused to somebody who may not read every page and
    every collection in it, on the command line, in the admin and over
    MCP, rather than handing them what they may not read.

*** What an agent waits for is decided by the session, not the person.
    A session made from a read-only token approves nothing, and one capped
    at reader or confined to part of the site approves only what that
    credential could.  The admin rebuilt every session as an administrator
    with no limits before asking, so a narrow credential could let a held
    payment or publish go ahead whenever its holder could elsewhere.

*** The person who started an agent run does not let its risky steps go
    ahead while somebody else could: a step that pays, publishes, deletes,
    sends or writes to another system waits for another person who may
    approve it.  Declining, answering a question and approving a change to
    a draft are as before.  When nobody else could decide, the person who
    started it still may; the audit record says the approval was made
    alone, and posture counts them (approval.alone).

*** A person approves anything a model wrote before it is published, and
    no setting turns that off; `quilzo review require --human-for-ai` is
    gone.  The person whose agent run wrote a draft approves it only when
    nobody else may publish, and is then recorded as approving alone.

*** `quilzo review approve` takes the publish permission, as the review
    screen already did; a reader's approval counted towards publishing.

*** A passkey has to ask for the PIN, fingerprint or face that unlocks
    it.  A key that was only touched signed an administrator or a member
    in, and an administrator's sign-in was still taken as a strong one: let
    through a lockdown, with its address exempted from the shield.  Adding
    a passkey and signing in with one now ask the browser to verify the
    person, and an answer without it is refused with what to set up; no
    setting turns this off.  A passkey already registered keeps working
    when it signs in with its PIN, fingerprint or face.  Each passkey
    records whether it can be synced to other devices and whether it is,
    the second brought up to date at every sign-in, and an administrator's
    passkey sign-in is in the audit log with both.

*** Somebody suspended by provisioning is refused at the passkey sign-in,
    as single sign-on refuses them, and the refusal is in the audit log.
    Their passkey still made a session, which every screen then refused,
    and exempted their address from the shield.  A suspension is a live
    deny of everything on the whole site; a deny of publishing, of one
    permission or of a person's own work, or one that has ended, no longer
    refuses a single sign-on or passkey sign-in as if it were one.

*** A passkey is added or removed only from a session that holds the
    whole person: their own role, over the whole store, with no limits on
    the token.  Any signed-in session could add one, including one from a
    token narrowed to part of the store, a lesser role or reading only,
    and the passkey then signed in as the person's full self and outlived
    the token.  Refusals, enrolments and removals are in the audit log.
    Passkeys now also work behind a proxy that terminates TLS when
    admin.behind_tls_proxy is set, as the session cookie and single
    sign-on already did.

*** Signing in with an identity provider or a passkey gives a session the
    highest role the person holds anywhere.  It took the role they held
    over the whole site, so an analyst, administrator of /@security alone,
    got a reader's session that refused them the security screens, as did
    anybody granted only part of the store.  The session's role is a cap;
    the policy still decides where it reaches.
*** What an app did through the agent interface, and what an agent did
    under its own identity, is recorded in that act's own audit record:
    granting, assistants, deciders, automations, findings and every other
    command an app can reach.  The record named no model, which the log
    refuses for an AI act, so only the interface's mcp.call record was
    kept.  It now names the app (mcp-client) or the agent as its model,
    and a test reads the source for any record that does not.
*** `quilzo records import` now checks every record against its
    collection's type, as adding one record does; one that does not fit
    imports nothing.


v0.3.0 — 20 September 2026
==========================

Ninety changes. Most came out of two sweeps: driving the running program
through a browser and a terminal rather than reading it, and an audit for
things this program declared and never executed. The second found more, and the
reason is consistent -- in every case the code was correct about its own job
and wrong about what another part was doing with it.

The security fixes here also shipped as v0.2.2 on the v0.2.x line. That release
notes what did not backport and why: three fixes are for code that does not
exist in v0.2.x, and two checks were left out because their subject does not
either.

Eighty-two changes.  Most of them came out of two sweeps: driving the running
program through a browser and a terminal rather than reading it, and an audit
for things this program declared and never executed.  The second found more
than the first, and the reason is consistent — in every case the code was
correct about its own job and wrong about what another part was doing with it.

** Declared, and never executed

*** `quilzo vault rotate` printed that it had rotated and had not.  It added a
    key and made it active; the objects wrapped under the old one were never
    rewrapped, so the key nobody could remove was the one everything still
    needed.  The rewrap existed, correct and complete, and had no caller.

*** Six settings reported success and did nothing.  Settable, validated,
    compliance-mapped, rendered in the admin, risk-acceptance recorded — and
    read by nothing at all.

*** Seven of eight agent archetypes could not do what they declared.  The
    executor was constructed with no resolvers, so the content scope in a
    manifest was never asked about anything.

*** A manifest restricted nothing and said otherwise on every screen.  Three
    more fields of the same struct followed: retrieval by type and locale, then
    by path, each carefully intersected and then consulted by nobody.

*** Two things were removed rather than wired, because a manifest may declare
    only what this build does: agent memory, which was declarable in three
    tiers, refused without a retention period, capped at ninety days, rendered
    in two interfaces and published on the agent card, and which nothing wrote
    or read; and the `http` and `process` integration kinds, which validated,
    displayed and switched, and which only `mcp` is ever called for.  A
    process integration's digest — the one supply-chain control that would
    matter if one ran — was checked for shape and never against a file.

*** A lockout nobody could see.  The throttle's snapshot was written for a
    command that does not exist and, as written, could not: the counters live
    in the memory of the process holding them, so the throttle's consequences
    were unobservable on every surface.  The state is on the admin's posture
    screen now — how many, how hard, how long,
    and no names, because the list of principals currently being attacked is
    itself worth protecting.

*** The content API validated content and wrote nothing down.  Every other
    write path records a binding saying *this content, this type, at this
    moment*; the API had its own copy of the gate and skipped that half, so
    content arriving over HTTP was the one kind with no record of what it was
    validated against.

*** A field recorded on every WebAuthn enrolment to answer a question no
    screen asked.

*** `quilzo studio` was a fourth surface with its own idea of how many
    sign-in attempts are free, built after the comment saying there is one
    place for that.

** Found by using it

*** Eighty valid form submissions, one address, none refused.  Three faults on
    the anonymous write path, including a demo form that could not be
    submitted at all, and a spam check that worked perfectly and counted
    nothing.

*** A peer pull read a ref, decided from it, and wrote without holding it.
    Sixteen concurrent writes against one base all passed the check and all
    committed; fifteen edits vanished.

*** An action carrying only a delegate ended the run.  Action.Done was
    `Op == "" && Tool == ""`, complete on the day it was written, and a
    supervisor's first hand-off named neither -- so the loop stopped, took
    whatever the supervisor had said to the delegate as its answer, and
    reported a clean run that did nothing.  The same field was missing when
    tool calls were added.  A test now compares what the dispatcher routes on
    against what Done knows about, in the package that adds the branch.

*** The host a tool call reaches was a field the model filled in.  It is
    resolved from the manifest now, and a request naming another host is
    refused.

*** Two keys can be the same setting.  `primary` and `primary.dark` are
    different strings and the same setting for the dark scheme, so `theme
    generate` and `theme import` both compared keys literally, never demanded
    `--replace`, and left half the old design in charge of the scheme fewer
    people look at.

** Agents

*** An agent can call a tool.  Everything guarding one already existed — a
    host allow-list with no wildcards, a vault-named credential, a client that
    refuses a tool the far side has newly advertised — and the dispatcher had
    no branch for it, so every authorised call fell through and answered that
    it was not implemented.  A tool call now taints the run for the same
    reason reading the store does, and more so: a third-party result is
    whatever a host returned on this request, reviewed by nobody here.

*** The watchdog could not see a single agent run.  internal/agentwatch exists
    to notice a model behaving badly -- an agent hammering at operations it
    keeps being refused -- and its only input is the audit log, filtered to
    events where the actor was a model.  Every `agent run` was recorded under
    the principal of whoever started it, with the manifest's name as a detail
    field, so:

        $ quilzo agents
        no model has acted in the last 24h0m0s

    with six runs in the log.  cmd/quilzo/assist.go already said why that is
    the wrong way round -- "the model is the actor, and it is recorded as one"
    -- and the MCP surface and the assistant both did it that way.  The runner
    built specifically for agents did not.

    The principal matters as much as the kind, because the watchdog buckets by
    it: with the person there, every agent one person runs would have merged
    into one report, and "the support bot is hammering at publish" and "the
    archivist is fine" would have averaged into a number about neither.

    A run without `--model` stays a person checking a manifest.  Nothing chose
    anything -- the plan is the manifest, every capability tried once in a
    fixed order -- so it cannot misbehave in the way the watchdog looks for.

*** An outcome is not a bill.  Both agent record sites asked Receipt.Billable
    and wrote "denied" when it said no.  Billable asks whether there is an
    outcome to charge for; denied means the gate refused something.  Three runs
    that could not reach the model were recorded as denied, and the watchdog
    then reported an agent repeatedly refused -- its signal for one that has
    not accepted the answer, which describes a broken endpoint not at all.

*** The same refusal read differently each time.  The agent gate's refusal
    naming which hosts an agent may reach was built by ranging over a map, and
    Go randomises map iteration on purpose -- so the same refusal, for the same
    reason, produced a different sentence on every run, into a tamper-evident
    log where it was then recorded under a different digest depending on
    nothing.

    Found by writing the check for the claim it breaks.  The AgentDojo figure
    is reported as a lower bound rather than an estimate, and that is only true
    if the gate answers the same way every time; nothing was checking.
    `scripts/agentdojo/passk.py` runs the whole corpus k times in fresh
    processes and in shuffled order, comparing every individual decision and
    its reason rather than the totals -- totals can agree while decisions swap.
    pass^10 is 1.00, and the same property is a unit test so a change that
    breaks it fails where it was broken.

    Not the reliability literature's pass^k, which asks whether an agent
    succeeds on all k attempts and needs a model.  Said in both the harness and
    its README, because a benchmark that quietly substitutes one number for
    another is worse than one that says what it lacks.

*** A tainted run says what tainted it.  The taint rule is the load-bearing
    part of the agent design -- anything produced after reading content
    somebody else may have written needs a person -- and the person was handed
    one bit.  The receipt said `tainted: true` and nothing else, so reviewing
    it honestly meant re-reading the site, which nobody does, and the approval
    became a click.

    Every source is recorded as it is read: a page by name, a listing by its
    ref, a tool by its name and the host it reached, and a delegate's sources
    folded up into its supervisor's.  It appears in the receipt, in the audit
    record, in the refusal that says the run cannot publish, and in what
    `agent run` prints.

    Found while driving it: the first version counted read events rather than
    distinct sources, so a run that read three things repeatedly reported "and
    6 more" and would have sent a reviewer looking for six pages that do not
    exist.

*** A supervisor can hand work to the agents it names.  Manifest.Delegates was
    validated, refused on anything that is not a supervisor, copied out of the
    supervisor archetype, and published on the A2A agent card under the
    governance gap the research calls delegation with accountability -- where
    another system reads it to decide what this one will do before asking for
    anything.  Nothing read the field, so a supervisor's whole reason for
    existing did not happen and the card said it did.

    A delegate runs as the intersection of its own manifest and its
    supervisor's, never as its own declaration, on what is left of the
    supervisor's budget rather than on what it declared.  What the child spent,
    what refused it, and whether it read untrusted content all come back to the
    parent -- the last of those because a tainted child answering into a clean
    parent would defeat the taint rule with one indirection.  The log names
    both agents, which is the half that makes it accountability rather than
    delegation.

*** Search returned nothing for a question.  The ranker required every term of
    a query to appear -- "Every term must appear.  Anything less returns most
    of the site" -- which is correct for two words that are both on the page
    somebody wants, and scores precision@1 of 1.00 on the judgement set.  It
    also means one word the page does not happen to use empties the result, so
    "how long does delivery take" matched nothing on a site with a delivery
    page.  An empty result reads to a visitor as "this site does not cover
    that" and to a model as a broken tool.

    It is BM25 now, which is what the conjunction was standing in for: drop the
    requirement and the longest page wins everything by having more of every
    word, and saturation and length normalisation are exactly the two parts
    that answer that.  The original six judgements still score 1.00; five
    sentence-shaped queries went from nothing at all to 1.00.

    Three other repairs were measured and rejected, and the reasons are
    recorded in the tests: a cosine floor from the second index (a stopword
    query scored higher than a real question), ranking by how many terms
    matched (it puts the About page above the Delivery page, because About
    holds "how", "does" and "take"), and requiring only the rare terms (rarity
    is not relevance -- one incidental rare word empties the result again).

*** Two working indexes no agent could ask.  Asked which page covered returns,
    an agent's only move was to list every page and read them one at a time
    until the budget ran out or it guessed right — while the full-text and
    vector indexes were built at publish, handed to the public API, and
    reachable from no agent at all.  Neither index is new; asking is.

** The design system you already use

*** `quilzo theme import` reads a W3C design token file — Design Tokens Format
    Module 2025.10 — and fills this site's tokens from it.  `theme export`
    goes the other way.  This ships nobody's palette and nobody's name: a
    design system's code licence usually permits a derived palette and its
    trademark policy does not permit the name, and two of the large ones
    separately forbid copying the look.  Reading the file the system publishes,
    on the machine of somebody entitled to use it, is a customer's own design
    system rather than an imitation of one.

    The pairing is written down and not guessed, because naming is where
    design systems differ most and a matcher that swapped the brand colour for
    the body text colour would produce a palette that is coherent, plausible
    and inverted.  `--list` prints every path in the file; `--skeleton` writes
    the left-hand side out.  Imported values go through the same contrast gate
    a hand-typed one does.

** Pictures and video

*** A picture can be replaced.  media.File.Supersedes was declared with a
    comment saying exactly why it matters -- "a page still pointing at last
    season's packaging shot is not a corrupted store, it is a store where
    nobody recorded that a newer one exists" -- and nothing in this program
    ever wrote it or read it.  A field whose own documentation describes a
    problem it does not solve.

    `quilzo media replace OLD NEW`, and a control on the media screen.  The
    succession is recorded on the newer file, because the older one cannot be
    rewritten; every draft reference is pointed at the replacement in one
    commit, on pages and on records, at whatever depth an author nested it;
    and the listing shows both directions, since Supersedes points backwards
    and the person looking at the retired picture is the one who needs to
    know.  A chain is ordinary and a loop is refused.

    One implementation for both surfaces.  Two that each walked the content
    would disagree -- one would read pages and not records, or match the bare
    id and not the "/media/..." spelling -- and the site would be half
    replaced by whichever door somebody used.  That is not hypothetical: it is
    what the image-rights gate did before internal/media held a single answer
    about what a reference looks like.

*** A photograph is stored the way up it is meant to be seen, and the metadata
    this pipeline promises to remove is removed on every path rather than
    most.

*** A picture can be chosen instead of typed as a hash, cropped in an
    interface that has no canvas, and told which part of it must survive a
    crop.  An edit derives a new object; the original stays, because nothing
    here is overwritten.

*** A video does not publish without captions, and the accessibility report
    says so either way.  A range request for two bytes of a film reads two
    bytes of it.

*** A picture asked of a model cannot arrive undeclared.  `quilzo media
    verify` checks every C2PA manifest, ours and theirs — the verifier this
    program already had and could not reach.

*** `quilzo studio` records the screen into the media library.  It is a fourth
    process rather than a route in the admin, because a capture surface needs
    JavaScript and the admin's policy is that it has none.

** Writing, and the interface

*** An editor can bold a word, make a list and link a phrase; edit from the
    page rather than from a form describing it; say "this paragraph is wrong";
    and do one thing to several pages at once.

*** `quilzo find` searches pages, screens, settings, types and files from
    anywhere.

*** A field can name another page, and publishing refuses one that names
    nothing.  A new record starts as the shape its type declares.  A listing
    goes where the page puts it.  A section keeps its name when the one above
    it is removed.

*** What points at this page, which nothing could answer before.  When did
    anybody last look at this.  Whose job a page is.

*** Four screens scrolled sideways on a phone and the links were sixteen
    pixels tall; five more scrolled sideways and then downwards because of it.
    The menu, the bar and the scrolling column were rebuilt over several
    passes until the page scrolls and the menu is the only thing pinned.

** Speed, each figure measured on a running site

*** A page cost the same whether the site had eleven pages or two hundred.
    A listing pages now: 6.83 ms -> 2.66 ms on a 211-page site.

*** The layout was re-parsed for every reader, and two thirds of the memory to
    serve a page went on building the same escaper: 714 KB across 1163
    allocations -> 234 KB across 737.

*** A page re-read the media library from disk for every row.

*** Seventy percent of every page was bytes nobody had to send.  Responses are
    compressed: 6,443 -> 1,955 bytes for the home page.  A browser holding two
    copies of a page sends two validators, and the list is now parsed the way
    RFC 9110 says.

*** The sitemap and the feeds transferred in full on every poll, and every
    listing-backed route served whatever the records were at boot.
    `/catalogue.json` — the largest thing the site serves, on the one route
    whose purpose is being polled by a machine — had no validator and no
    cache directive at all.

*** Six servers had one timeout each and nothing bounding the rest.

** Honesty

*** The security report told you to run commands this program does not have.

*** Every way to publish runs the same checks, and a test now walks the source
    and fails on a surface that does not.


v0.2.2 — 20 September 2026
==========================

A backport release. Everything here was fixed on the development branch after
v0.2.1 and is applied to the v0.2.x line rather than left to wait for the next
feature release.

** The publish path

*** Four ways to publish ran four different sets of checks.  Not a subset
    each: a different set each.  The browser published a draft the command
    line refuses, with an unsubstantiated claim on it; a message in a chat
    room went straight to live having run the type gate and accessibility and
    nothing else.  All four run the same table now, and a test walks the
    source and fails on a surface that does not.

*** Two gates in the browser exited like gates that had passed.  Both returned
    an empty slice on every error, and an empty slice reads to the caller as
    "nothing is wrong": a page that could not be rendered published clean.

*** A proposal is put through the publish gates before anybody accepts it.
    The checks were only asked at publication, which for a model's draft is
    the last possible moment and the wrong one.

** Access

*** A binding scoped to part of the site meant nothing on either surface.
    Every handler that read a page name asked whether the caller could act on
    "/", and an admin denied author on /about still holds admin on "/" -- so
    the deny never matched.  The browser is where that rule is displayed, and
    somebody reads it there and believes it.

*** A chat user published the operator's draft.  The draft ref is one ref for
    the whole store, and the chat publisher read it, added the message's page
    and published the lot.

*** Four ways a credential was less than it said it was.

*** A lockout nobody could see.  The throttle's consequences were
    unobservable on every surface.

** Data

*** An erasure reported success and erased nothing.  The JSON branch of
    `quilzo form erase` answered {"matched": n, "removed": true} and returned
    -- before the delete loop and before the audit record, both of which came
    after it.

*** Eighty valid form submissions, one address, none refused.  Three faults on
    the anonymous write path, including a demo form that could not be
    submitted at all and a spam check that worked perfectly and counted
    nothing.

*** A peer pull read a ref, decided from it, and wrote without holding it.
    Sixteen concurrent writes against one base all passed the check and all
    committed; fifteen edits vanished.

*** The vault rotated nothing.  `quilzo vault rotate` added a key, made it
    active, printed success, and left every object wrapped under the old one
    -- so the key nobody could remove was the key everything still needed.

** Agents

*** A manifest restricted nothing and said otherwise on every screen, and
    seven of eight archetypes could not do what they declared.

*** The content API validated content and recorded no binding, so content
    arriving over HTTP was the one kind with no record of what it was
    validated against.

*** The same refusal read differently each time.  The refusal naming which
    hosts an agent may reach ranged over a map, so it produced a different
    sentence -- and a different digest in a tamper-evident log -- on every run.

** The audit log

*** Two acts went unlogged because of the name of a field, a broken chain was
    not mentioned, and a rule written to catch an empty log could never fire.

** Not in this release, and why

*** Tool calls and delegation.  An agent's declared tool host was a field the
    model filled in, and a tool call did not taint the run.  Neither exists in
    v0.2.x: Manifest.Tools and Manifest.Delegates were both added afterwards.

*** The "references" gate refuses a page whose field names a page that is not
    being published.  It is built on the `page` field kind, which v0.2.x does
    not have, so there is nothing for it to check -- and a gate that cannot
    fire is worse than an absent one, because the table says it ran.

*** search_pages and similar_pages are not in the archetypes or in the
    executor's list here.  They were wired to the agent surface afterwards,
    and listing them would let a manifest validate while declaring a
    capability this build cannot carry out -- the exact failure that list was
    added to prevent.


v0.2.1 — 8 September 2026
=========================

*** The binary in the container image knows which release it is.  The release
    workflow passed `--build-arg VERSION=` and the Dockerfile declared no such
    ARG, so it was discarded without a word: every published image carried a
    binary answering "dev" to `--version` while the label beside it said the
    real number.

    It matters more here than it would elsewhere.  This program's argument for
    generating its bill of materials from the binary rather than from the
    source tree is that the binary knows what it is -- so `quilzo compliance
    sbom` run inside the image produced a document naming version "dev", which
    is the failure that reasoning exists to prevent.

    Found by running `--version` against the published v0.2.0 image rather than
    by reading the workflow.  The release now checks that the image agrees with
    the tag, because a build argument the other side ignores is exactly the
    kind of mismatch nothing was comparing.


v0.2.0 — 8 September 2026
=========================

A security release. Everything here was found by testing the program rather
than by reading it, and each item was reproduced before it was fixed.

** Fixed, and each was reachable

*** A value could break out of the attribute it was rendered into.  The
    engine decided which HTML context a value landed in by scanning back 256
    bytes for a `<`; pad the output past that with one ordinary field and an
    attribute looked like text, where quotes are not escaped.  The value then
    closed the attribute and opened an event handler.  Both fields are
    ordinary content, so no template author had to make a mistake, and the
    shipped demo theme has that exact shape.  `quilzo scan` did not catch it.

    Context is tracked as the output is produced now, one pass, no window.
    Two more ways out closed with it: a quoted `>` in an earlier attribute
    made the engine think the tag had ended, so an `href` was never
    scheme-checked and `javascript:` survived; and an unquoted attribute value
    could start a second attribute, because a space ends one and escaping
    quotes does not help.  A value inside <script> or <style> is refused
    rather than escaped, because entities are not decoded there and the
    escaping is inert.

    All 49 shipped templates render byte-for-byte as before.

*** Static copies carried no Content-Security-Policy at all.  Every `ipfs
    write`, every archived deposit, every site pinned to IPFS.  The policy was
    computed for each response and then dropped, because a file cannot send a
    header.  It is written into the document now.  This is what the template
    fix above was relying on: on the served site `script-src 'none'` stopped
    the attribute breakout, and on the copy somebody archives nothing did.

*** A captured ActivityPub inbox request could be replayed forever.  The
    draft-cavage signature format carries `created=` as a parameter that only
    enters the signing base when the signature says it covers it -- and it was
    read either way.  Append `,created=<now>` to a captured request and the
    base is unchanged, the signature still verifies, and the age check reads
    the attacker's number.  A three-month-old Follow, Undo or Delete replays
    as the actor whose request was captured.  Only a timestamp the signature
    covers is read now, and `expires` is enforced, which it never was.

*** --own-only was never enforced.  It was offered on the command line and as
    a checkbox, both confirmed the restriction had taken effect, and the
    function that resolves such a binding had no caller: anybody granted
    `author --own-only` could edit every page in the store.  The flag is
    withdrawn and refuses rather than being ignored.  Nothing loses access,
    because nothing was restricted.  Use `--on /path`, which is enforced.

*** Twenty of twenty-three agent operations checked no authority.  An author
    could read any page in the unpublished draft, dump the whole site, and
    pull the posture, inventory, integrity and agent-activity reports that the
    admin restricts to an administrator -- on the stated grounds that a list
    of where the defences are thin is a target list.  Every operation declares
    a role now and is checked centrally, and a server with no authorisation
    hook refuses rather than serves.

*** A peer could write a file anywhere on disk.  Objects arriving from a peer
    were checked to hash to the id offered and parsed no further, so a tree
    whose entry names were `../../../../tmp/pwned` was stored, read back
    without re-validation, and joined onto the output directory by `quilzo
    export`.  The store validates a tree it is handed, and the exporter checks
    the join regardless.

*** AI Act Article 50 was enforced on one surface of three.  README said
    publishing unmarked content is refused, not warned about.  The command
    line never checked, so `quilzo publish && deploy` shipped unmarked content
    and exited zero; the agent interface skipped its check whenever the check
    errored rather than when it passed.  Both refuse now.  A person can
    override with `--force-unmarked --reason`, an agent cannot, and the
    override is recorded with the gates it waived.

*** Publishing from the browser left no audit record.  Not the publish, not
    the override, not the reason -- while the screen labelled the reason box
    "Recorded."  The command line had recorded all three all along, so the log
    held every publish made from a script and none made from the browser.

*** An author could rewrite content types through the browser and was refused
    from a script.  Binding or unbinding a type turns off the gate that checks
    content, so an author who can do it has escaped the rules that constrain
    authors.  The browser needs publish for it now, as the command line always
    has.

*** The session cookie was not marked Secure behind a TLS-terminating proxy,
    which is the ordinary deployment.  Its value is the API token.  Set
    `admin.behind_tls_proxy` when something in front of this speaks HTTPS.

** Usability

*** --token works on every command.  It was read globally and removed nowhere,
    so seven of eighty-one commands accepted it and the rest exited 1 with
    "flag provided but not defined".  Among the rest was `add`, the commonest
    write -- and every refusal in the program ends by advising `--token`.

** Honesty

*** Retention is enforced when something enforces it.  The form package said
    submissions are removed "without anybody asking" and nothing removes
    anything on its own.  `quilzo form expire` does it; a cron entry or systemd
    timer is the intended arrangement, and without one submissions past their
    declared ceiling are kept.  A declared retention period and an enforced one
    are different things.


v0.1.0 — 7 September 2026
=========================

The first tagged release. Everything below is in it; there was no release
before this one, so nothing here is a change from something you were running.

Why 0.1.0 and not 1.0.0.  A 1.0 in semantic versioning is a promise about
compatibility, and the honest position is that this project has one
maintainer and no users yet whose upgrade this would be.  GOVERNANCE.md names
the single-maintainer risk at the top of the file rather than at the bottom,
and a 1.0 would be the one immodest sentence in a repository that has spent
some effort not writing those.

The asymmetry decided it.  A 1.0 can follow whenever the compatibility
promise is one somebody is willing to keep.  Releasing 1.0 first and walking
it back is not a thing that can be done.

What a release gets you that a clone does not: static binaries for six
platforms, a multi-arch container image on ghcr.io, a CycloneDX bill of
materials generated by the binary rather than from the source tree, a
cryptographic inventory including post-quantum status, SHA-256 sums, and build
provenance attested to the workflow run that produced it.  The build refuses
to ship from a modified tree, and refuses to ship if the third-party
dependency count is anything but zero.

** Design and templates

*** A page chooses its layout.  templates/ is a directory of layouts and a
    page names one in its own body.  A page naming a layout the site does not
    have is refused at publish rather than rendered through the default,
    because a page nobody designed with no message anywhere is worse than a
    failure.

*** A page's shape is content.  Nineteen kinds of section — hero, features,
    metrics, bar and ring charts, split, gallery, carousel, video, steps,
    timeline, quote, logos, pricing, questions, table, people, prose, notice,
    call to action — ordered by the content rather than the markup, so
    reordering a homepage is an edit that rolls back like any other.

*** Themes are a checked token set.  Colours in both schemes, three type
    stacks, the type scale, the measure, corner radius, spacing density,
    breakpoints and a two-stop gradient.  Colour contrast is computed over
    the generated stylesheet and a theme that puts body text below 4.5:1 is
    refused at publish.  This closes a gap the program used to disclose: it
    listed contrast under what it could not check, and it generates the
    stylesheet now.

*** Typefaces are served from the site's own origin.  Put a .woff2 in
    templates/fonts/ and it is validated and served at /fonts/.  There is
    deliberately no way to name a font on another host.

*** quilzo template adopt converts a template written for another system —
    Liquid, Twig, Jinja, Django, Handlebars, Mustache, Go templates, Hugo.
    Script, event handlers, executable URL schemes and external stylesheets
    are removed; anything that could not be translated is named with the
    shape it should become, and the layout is not written while any remain.

** The assistant

*** A proposed template that can execute is refused.  The assistant already
    refused {% raw %}, on the grounds that turning escaping off is a human
    decision.  It now refuses a proposal carrying a <script> element, an
    inline event handler or a javascript:, vbscript: or data: URL, for the
    same reason and using the same patterns quilzo template adopt strips from
    a foreign template.

    This closes a gap between the two ways markup written elsewhere gets into
    the template store.  adopt treated it as untrusted and stripped it; a
    model's proposal was checked for {% raw %} and nothing else, so a
    prompt-injected model could put script into a layout that renders on
    every page using it.  Content-Security-Policy stopped it running, which
    is defence in depth working rather than the control being present.

    Adding script to a template by hand still works.  That is the decision
    this refuses to make on your behalf, not one it takes away.

** Structured data

*** WebPage, Article and BreadcrumbList for ordinary pages, built in Go
    because JSON inside a script element needs JSON escaping and the template
    renderer escapes for HTML.  Product data on detail routes already
    existed.

** Access

*** --own-only is withdrawn, and refuses rather than being ignored.  It was
    offered on the command line and as a checkbox in the admin, both said the
    restriction had taken effect, and nothing enforced it: the function that
    resolves an own-only binding had no caller, and every check dropped the
    field.  Anybody granted `author --own-only` could edit every page in the
    store.  Nothing loses access now, because nothing was restricted.  Use
    `--on /path`, which is enforced.  A binding written before this is shown
    as never enforced rather than silently as a restriction.