Commit Graph
128 Commits
Author SHA1 Message Date
dckc ff2e7841f2 capsite/webapp: postForm, a multipart POST that can echo a cookie into a field
Some sites check a double-submit token: the session value sent as a
cookie and again in the form body. postForm(fields, echoCookies) builds
a multipart body and copies the named cookies (from the header store,
at send time) into fields, so the caller never sees the value.

It returns a Reply instead of throwing on non-OK: status, content type,
content-disposition, the redirect location with its query stripped, and
the body as bytes (the caller decodes, e.g. per a charset the body
declares). Redirects are not followed, so an expired session shows up
as a 3xx. Set-Cookie is not passed back. Errors name keys, never values.

Generated-by: Claude Opus 5.5
2026-10-11 14:22:09 -05:00
dckc 7876938f3e capsite/docs: describe the wiring diagram for screen readers
The SVG gets role="img" with a <title> and a <desc> that says in prose
who refers to what, since the arrows carry the meaning and are invisible
to assistive tech. The rendered PNG is unchanged.

Generated-by: Claude Opus 5.5
2026-10-09 02:32:22 -05:00
dckc a921d65021 capsite/docs: cookie-rotation wiring diagram (PNG)
A headless-Chromium screenshot of docs/cookie-rotation-wiring.html.

Generated-by: Claude Opus 5.5
2026-10-09 02:32:22 -05:00
dckc 055b572297 capsite/docs: cookie-rotation wiring diagram (source)
Who holds what after mint: Alice holds the store id (rotate, revoke),
diigo-cache and a leaked-id Mallory hold only the instance, whose
init.headerStore reaches the store's read-only facet. Drawn as an SVG
page in the style of MarkM's ERTP slides.

Generated-by: Claude Opus 5.5
2026-10-09 02:32:22 -05:00
dckc 4dd3fe6bde capsite/diigo-cache: the wire page hands the owner the store id
webapp's mint now answers a store id (x-webapp-store-id) beside the
instance id: authority to rotate or revoke the diigo cookie. The wire
page shows it apart from the instance id, with its own copy button,
petname, and password-manager save; the cache never gets it. A webapp
that sends none gets a note on the page and a warning in the log.

Generated-by: Claude Opus 5.5
2026-10-09 02:32:22 -05:00
dckc 3f711a5f4b capsite/webapp: the cookie's store is the owner's, apart from the instance
Letting the instance id rotate the cookie let its holder (the cache, or
whoever learns the id) swap in another account's session, which the
owner's logout would not stop. Now mint makes two things: the instance,
and a header store (lib's MapStoreDo, shaped ("cookie", { keyShape:
"cookie", valueShape: M.string() })). The instance id is authority to
use the cookie; the store id, for the account owner, to rotate (PUT
/store/<storeId>/cookie, or WebAppFactory.store) or revoke (DELETE).
Instances read the store's read-only view at every send, via init's new
headerStore.

This replaces the in-instance cookie store and its migration: instances
minted earlier keep their cookie in init.headers, untouched, and simply
cannot rotate. The MapStoreDo class arrives as wrangler migration v2
(additive).

Generated-by: Claude Opus 5.5
2026-10-09 02:32:22 -05:00
dckc 357e4561c4 capsite/lib: endo's own matcher data, smallcaps, and MapStoreDo
Matchers are now exactly what @endo/patterns makes: tagged records as
from pass-style's makeTagged (M.string() is match:string with payload
[], M.any() is match:any with payload undefined). Structured clone drops
their symbol-keyed and non-enumerable properties, so they cross DO
storage and Workers RPC in marshal's smallcaps encoding (the subset
patterns need).

MapStoreDo is a MapStore as a DO of its own: its creator fixes label and
(encoded) shapes once; its id is full authority, and readOnly() hands
out the queries alone.

Generated-by: Claude Opus 5.5
2026-10-09 02:32:22 -05:00
dckc 10414c2ce6 capsite/webapp: rotate the cookie in place
The cookie moves out of the immutable init into a MapStore (key shape
"cookie", value M.string()) in the instance's DO storage. Handles get
its read-only view via the new WebApp.withHeaderMap, which reads at
every send, so a rotation reaches handles already out and the webapp
id, and with it the cache's wiring, stays put (checklist goal 6).

Whoever holds the id can rotate, by RPC (WebAppFactory.cookies(id)) or
POST /at/<id>/cookie; the diigo account owner revokes by logging out.

Durable state: instances minted earlier keep the cookie in
init.headers. The first handle() moves it to the store and rewrites
init without it, in one transaction. Code from before this commit
would then see no cookie, so rolling back past it needs the reverse
move.

webapp gains a package.json and a vitest workerd pool for the tests.

Generated-by: Claude Opus 5.5
2026-10-09 02:32:22 -05:00
dckc 9179bdbf38 capsite/lib: MapStore and patterns work-alikes
Enough of @agoric/store's MapStore and @endo/patterns' M for the
credential-rotation design: makeMapStore(label, { keyShape, valueShape },
kv) keeps entries in a DO's sync KV under store:<label>:, and readOnly()
hands out the queries alone. Facets are workerd RpcTargets in place of
endo's Far.

Unlike endo, error messages never include keys or values: the values
here are credentials, and messages cross RPC into logs.

Generated-by: Claude Opus 5.5
2026-10-09 02:32:22 -05:00
dckc 1d8a220bbc capsite: checklist: 2-iv, the weekly draft works in dev
Generated-by: Claude Opus 5.5
2026-10-09 02:12:41 -05:00
dckc 68132ff4b3 diigo-week: a dashboard card drafts last week in one click
The card shows last week's issue with a link to it, or a button to
draft it. Drafting a week that already has an issue (draft or
published) is refused, with a link to that issue, rather than making
a duplicate.

Generated-by: Claude Opus 5.5
2026-10-09 02:12:41 -05:00
dckc 9c09e8c5a4 capsite: checklist: 4b, consider scruffier tags
Generated-by: Claude Opus 5.5
2026-10-09 02:12:41 -05:00
dckc 94a059af25 diigo-week: draft a potluck issue from a week of diigo bookmarks
An EmDash plugin (native, content:write only) with one admin page:
a diigo username and an ISO week. It reads diigo-cache's public page
(?in=<week>&by=created) as microformats, follows rel=next, and
creates a draft in issues shaped like seed/issues.json.

Transport is a DIIGO_CACHE service binding rather than ctx.http.fetch:
EmDash's SSRF guard refuses localhost, and the binding names one
worker. Logic lives in src/week.ts with the fetcher passed in; tests
read fixtures in diigo-cache's markup. The checklist's goal 2 now
names potluck; DiigoBridge waits.

Generated-by: Claude Opus 5.5
2026-10-09 02:12:41 -05:00
dckc be796d9d0d diigo-cache: the public page is an h-feed of h-entry bookmarks
Microformats are the public page's API for other sites (potluck
first): each bookmark is an h-entry with u-bookmark-of, p-name,
p-summary, a p-category per tag (linked to a tag search),
dt-published (bookmarked) and dt-updated (last edited). Prev/next
links carry rel=prev/next, so a reader can follow a filter's pages.
Tests parse the page with microformats-parser to pin the contract.

Generated-by: Claude Opus 5.5
2026-10-09 01:49:40 -05:00
dckc c66aaea366 potluck, diigo-cache: docs one line per paragraph; potluck deploy caveat
Obsidian and GitHub render each newline as a line break, so the
hard-wrapped READMEs and CONTRIBUTINGs rendered ragged. Whitespace
only, except a note in potluck's CONTRIBUTING that other workerd
hosts are untested: they'd have to supply the D1 and R2 bindings.

Generated-by: Claude Opus 5.5
2026-10-09 01:30:56 -05:00
dckc a25bd699e1 potluck: README: workerd, not a vendor
Generated-by: Claude Opus 5.5
2026-10-09 01:29:31 -05:00
dckc ea3e256bb8 diigo-cache: README: workerd, not a vendor
Generated-by: Claude Opus 5.5
2026-10-09 01:28:50 -05:00
dckc 0af7a3ea6c diigo-cache: README sells and gets to TADA; CONTRIBUTING updated
New README: two sentences, the dev run through connect, fill, and a
filtered public page, then features. CONTRIBUTING gains the 1d
filters and its own run and deploy sections instead of pointing at
the checklist; links to the old README now point at CONTRIBUTING.

Generated-by: Claude Opus 5.5
2026-10-09 01:27:45 -05:00
dckc 9719fd0aa8 diigo-cache: README becomes CONTRIBUTING; AGENTS.md links to it
Generated-by: Claude Opus 5.5
2026-10-09 01:27:12 -05:00
dckc bce2424cc0 potluck: README: a heading that says something
Generated-by: Claude Opus 5.5
2026-10-09 01:25:47 -05:00
dckc 7f1e19fe85 potluck: README: two sentences, then getting started, then features
Generated-by: Claude Opus 5.5
2026-10-09 01:25:13 -05:00
dckc 8a812d76e3 potluck: README sells and gets to TADA; CONTRIBUTING has the rest
The README was EmDash's template blurb. Now it says what potluck is
and its first block runs from a fresh clone to /issues, using a new
`pnpm seed:issues` script that applies seed/issues.json to the local
D1 database. CONTRIBUTING covers how it's put together, seeds, checks,
the EmDash rules, and deploying; the template's stale notes (Inter,
three-column articles) are gone. AGENTS.md is now a symlink to it.

Generated-by: Claude Opus 5.5
2026-10-09 01:24:10 -05:00
dckc 002f08b456 diigo-cache: date windows and search on the public page (1d)
/public/<username> takes days, after, before, in (year, month,
quarter, ISO week), by=updated|created, and q (words, "phrases",
#tags, -negation, gmail/Slack/GitHub date operators). JSON answers
echo the parsed filter; a malformed one is a 400.

Filters query the stored items in sqlite through the read-only
viewer instead of walking diigo pages: the public page keeps no
fetch authority, and a filled cache already holds every item.
Results say `complete` when every diigo page is stored; freshness
is 1f's head refresh. So the fetch budget and the O(log pages)
walk test go, and the fake deep account shrinks to 300.

Generated-by: Claude Opus 5.5
2026-10-09 01:17:03 -05:00
dckcandClaude Opus 5.5 efb54ad4cc docs: Good Fences Make Good Agents (design story + IBIS)
The story behind good-fences: lend a governed mower over the fence to a
neighbor whose tenants are model-written code, with the design IBIS
worked out so far. Previously an untracked note at the workspace root.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-09 01:00:15 -05:00
dckcandClaude Opus 5.5 8c110098ff feat(good-fences): a Think agent mows with a codemode mower connector
Neighbor is a Project Think agent on gpt-oss-120b. Its execute tool
runs model-written code in a dynamic worker; MowerConnector puts the
mower there as the `mower.*` namespace (start/push/turn/stop, each
taking an args object). makeMower is the Logo-turtle mower on a
wrapping yard; for now it lives inside the agent's DO.

Getting to green, one symptom at a time:
- chatted instead of acting: say it acts by running code
- reply cut mid-sentence: maxOutputTokens 4096 via beforeTurn
- execute failed creating codemode's runtime facet ("Incorrect type
  for the 'class' field"): declare CodemodeRuntime as a DO class
- looked for the mower in the workspace filesystem: activeTools
  ["execute"], and name the namespace in the prompt
- found the mower, then asked for lawn dimensions: "don't ask me
  questions: put it to use"

Green: 36 mower calls, a 30-cell rectangle.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-09 00:35:56 -05:00
dckcandClaude Opus 5.5 d61cb8c9a1 test(good-fences): lent a mower, a Think agent mows (red)
The first end-to-end test: a Project Think agent, on the real model,
writes code that runs in a dynamic worker and drives a mower; the test
counts the cells cut, and saves the turn to itest-output/.

Installs @cloudflare/think, @cloudflare/codemode, agents, ai,
workers-ai-provider, zod. The model-test worker now has a main entry,
the Neighbor DO, and a LOADER binding.

Red: src/worker.ts doesn't exist yet.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-09 00:31:25 -05:00
dckcandClaude Opus 5.5 a2ca59a27e feat(good-fences): code-mode prompt; the model writes code to mow
codeModeMessages turns what I say plus `about` into a prompt: a
neighbor persona, rules for the reply (one async arrow function over
`env`), and the lent things' types. Things are remote and calls on one
are delivered in order, so a lamp example shows pipelining: await only
for a value, or on a batch's last call. With only the rule, the model
awaited every call; with the example, it pipelines.

aboutMower gives the Mower type with Promise returns. The model test
checks the code calls only Mower methods, pushes, and awaits less often
than it calls; it saves the code to itest-output/ via a reporter, since
the test runs in workerd, which can't write files. Model tests get 60s.

Green: both model tests.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-09 00:21:44 -05:00
dckcandClaude Opus 5.5 4a85143a66 fix(good-fences): give the model room to answer
gpt-oss-120b reasons before it answers, and Workers AI's default
max_tokens (256) ran out mid-thought: finish_reason "length", content
null. Ask for 4096, and when there's no reply text, say which choice
and message keys came back instead.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-09 00:21:44 -05:00
dckcandClaude Opus 5.5 37faa92649 test(good-fences): lent a mower, the model writes code to mow (red)
Code mode (2c): the harness turns what I say plus `about` into a prompt;
the model replies with `(env) => ...` code. Checked textually for now,
since workerd won't eval it: the code pushes `env.mower` and calls only
methods in `about`. It also prints the code, to see what the model writes.

Red: src/code-mode.ts doesn't exist yet.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-09 00:01:39 -05:00
dckcandClaude Opus 5.5 ec24f3e8e4 feat(good-fences): complete(messages) on a Workers AI model
makeWorkersAiModel narrows the AI binding to one model's chat
completion: the harness gets `complete`, not `run` on any model at
any cost. gpt-oss-120b replies in the OpenAI shape (checked with a
one-off probe), so the text is choices[0].message.content; anything
else throws, naming the keys it got.

Green: the model counts 3 rs in strawberry.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-08 23:41:18 -05:00
dckcandClaude Opus 5.5 47a2337afe test(good-fences): the model counts the rs in strawberry (red)
A first integration test of the model call: Workers AI through the AI
binding, behind the same `complete(messages)` interface as the sim's
fake model. Opt-in (`pnpm test:model`) since it needs `wrangler login`
and costs money.

Red: src/model.ts doesn't exist yet.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-08 23:36:46 -05:00
dckcandClaude Opus 5.5 9b12943be4 test(good-fences): show which mower the neighbor gets
The chat and load arrows drew `things: { mower }` whether I lent the
raw mower or the governed one. Name objects by the reply arrow that
introduced them, so the diagram reads `things: { mower: governed }`
and lending the raw mower would fail the trace comparison.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-08 23:27:13 -05:00
dckcandClaude Opus 5.5 9a3e7348c6 test(good-fences): lend-the-mower journey (red)
The spec is a sequence diagram of the a2030.md story: I lend a governed
mower; the neighbor's harness shows the model only `about`, runs its
code as a tenant, and the governor stops the tipsy tenant at a 2-cell
fence. The test runs the actors through a causal tracer and compares
the trace with the diagram, then checks the yard and what the model saw.

The tracer comes from agoric-sdk PR #12895, extended to name
participants made mid-sequence (governor, tenant) and to draw thrown
errors as replies.

Red: src/ doesn't exist yet (ERR_MODULE_NOT_FOUND for src/fence.ts).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-08 22:17:48 -05:00
dckc b4ab820486 capsite/docs: references to DOs in Discord messages
How a Discord message could carry references to our DOs, after
@endo/chat: names in the text, references beside it in a mailbox DO,
an Adopt button answered ephemerally, and per-user petstores. Webkeys
in markdown work but disclose the capability to every channel reader
and to Discord itself; ephemeral replies still pass through Discord,
so references should never cross it at all.

Generated-by: Claude Opus 5.5
2026-10-08 16:19:45 -05:00
dckc 93d92d0207 capsite: checklist: run potluck, not madmode
The EmDash site is now potluck/, created fresh with create-emdash.

Generated-by: Claude Opus 5.5
2026-10-07 20:33:08 -05:00
dckc 1aeb3a21db capsite: toybox user stories and goals (T1-T3)
toybox-stories.md: the weekly roundup (#23), album-to-album sync
(endojs/playground#16), and drag-to-archive.org (#118), with the needs
they share. The checklist gains the toybox tree: a changed signal (T1),
Blockly composition by petname (T2), and confined code in Dynamic
Workers (T3).

Generated-by: Claude Opus 5.5
2026-10-07 20:33:08 -05:00
dckc a086a8c495 capsite: checklist: 1g, notes on public items are public too
The public cache keeps public items whole, private annotations and
comments included (2,525 of 14,645). Dan finds that acceptable; the
goal is to finish thinking it through, not to fix a leak.

Generated-by: Claude Opus 5.5
2026-10-07 20:33:08 -05:00
dckc 45223eb0b7 capsite: checklist: reconcile 1d with 1f; decide DiigoBridge's fate (2-iii)
1d's query.ts plans to filter while walking diigo's pages, but a filled
cache holds every item: choose before implementing. madmode's public
reads need no cache id, so DiigoBridge's cache-id TODO is moot unless a
private-reading caller wants it.

Generated-by: Claude Opus 5.5
2026-10-07 20:33:08 -05:00
dckc fd7fd5728d capsite/potluck: a weekly zine of issues, not a blog
- issues collection (ISO-week slug, week, dek, rich-text body); home,
  /issues, and /issues/<week> pages; seed/issues.json holds three
  sketched issues (W38-W40) from public diigo bookmarks, editorial
  lines marked (draft); issues-sketch.md is the dry run behind them
- microformats: h-feed of h-entry lists; an issue is an h-entry
  (p-name, dt-published, p-summary, e-content); off-site links render
  as h-cite, /tag/<tag> links as rel-tag p-category (CiteLink.astro)
- diigo tags: shown on each bookmark and attached to issues via the
  tag taxonomy (now posts + issues); /tag/<tag> lists issues
- demo blog content gone: posts dropped from the seed, home page lists
  issues, site title/tagline, footer About widget, and About page
  rewritten; nav is Home / About / Issues; search and RSS cover issues
- .column container gives potluck's pages the header's margins

Generated-by: Claude Opus 5.5
2026-10-07 20:31:54 -05:00
dckc b9f3156d6d capsite/potluck: plain style after kmjn.org's "Plain HTML with a few bureaucratic gestures"
https://www.kmjn.org/notes/modern_plain_html.html and its stylesheet:
system fonts (Verdana; no Google webfonts), one 800px column,
line-height 1.35, links in the text color with a dotted underline, no
rounding, shadows, or tracking; the header is plain, not sticky glass.
Token overrides live in theme.css; per-page scoped styles unchanged.

Generated-by: Claude Opus 5.5
2026-10-07 20:00:29 -05:00
dckc 4b4ef8fea6 capsite: potluck, a fresh EmDash site (cloudflare:blog out of the box)
pnpm create emdash@latest potluck --pm pnpm --install --yes, per
https://docs.emdashcms.com/ ("Create your first EmDash site"); the
--yes defaults are cloudflare + blog. Replaces the old madmode/
starter (never committed).

Generated-by: create-emdash 1.2.0, run by Claude Opus 5.5
2026-10-07 19:57:43 -05:00
dckcandClaude Opus 5.5 af8dd75fd1 capsite: public-origin candidate: sync (as in bib sync #195)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-07 13:03:27 -05:00
dckcandClaude Opus 5.5 45a2524542 capsite: more public-origin candidates: syndicate, peers
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-07 13:01:42 -05:00
dckcandClaude Opus 5.5 9dbf34efa7 capsite: pesos.madmode.com as the candidate public origin
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-07 13:00:40 -05:00
dckcandClaude Opus 5.5 9c3e6fa396 capsite: fedi-cache keeps the token; ActivityPub sources come later
The existing export (mastodon-archive) reads favourites, bookmarks,
and mentions, which ActivityPub doesn't carry. So the client API with
a narrow read-only token (held in webapp) is the primary source;
items are still keyed by ActivityPub uri, and outbox/follower-actor
sources move to a later portability goal.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-07 12:58:55 -05:00
dckcandClaude Opus 5.5 1e6436e089 capsite: social.coop tree at the ActivityPub level (fedi-cache)
Mastodon is one implementation; the cache speaks the protocol: outbox
for backfill, and fedi-cache as a follower actor so Update/Delete are
delivered rather than polled for. Its own keypair replaces an access
token, so webapp drops out of this one.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-07 11:16:05 -05:00
dckcandClaude Opus 5.5 99ce5b6964 capsite: goal trees for a zotero cache and a social.coop cache
Both follow the diigo-cache pattern (webapp holds the credential,
read-only and minted narrow at the source; the cache serves a public
page). Zotero's versioned sync answers diigo's stale-head problem;
Mastodon's doesn't announce edits/deletes, so its tree carries that
as a design question.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-07 10:21:24 -05:00
dckc 279ddb4caa fixup! diigo-cache: the public page only reads
Comments written for the reader: no restating what the shebang says, no traces of the design conversation; the publish comment no longer mentions a projection that no longer exists.

Generated-by: Claude Opus 5.5
2026-10-07 09:46:21 -05:00
dckc 7c0d1d62c6 fixup! diigo-cache: RSS in diigo's export shape, from cached items
Comments written for the reader: no restating what the shebang says, no traces of the design conversation; the publish comment no longer mentions a projection that no longer exists.

Generated-by: Claude Opus 5.5
2026-10-07 09:46:21 -05:00
dckc 697fd95514 fixup! diigo-cache: RSS in diigo's export shape, from cached items
compare-export.ts: the manual is a HELP string at the top of the module
(a docstring, as a value) and main prints it on bad usage. No reading
its own source, no regex, one copy; @file points at it.

Generated-by: Claude Opus 5.5
2026-10-07 09:44:26 -05:00