@vibes.diy/prompts 14.1.33 → 14.1.35

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/llms/access.md CHANGED
@@ -22,9 +22,11 @@ and keep loading and signed-out states distinct from denial.
22
22
 
23
23
  `(doc, oldDoc, user: UserContext | null, ctx: Helpers) => AccessDescriptor` where `doc` is the document being written, `oldDoc` is the previous version (null for new documents), `user` is the authenticated user or `null` for anonymous requests, and `ctx` provides server helpers for checking materialized state.
24
24
 
25
- **UserContext:** `{ userHandle: string, displayName?: string }` — `userHandle` is stable unique id (use for all auth checks), `displayName` is display only (never use for identity checks).
25
+ **UserContext:** `{ userHandle: string, displayName?: string, backend?: "scheduled" | "onChange" | "fetch" }` — `userHandle` is stable unique id (use for all auth checks), `displayName` is display only (never use for identity checks), and `backend` is present only when the app's own `backend.js` issued this write, naming the handler that did (`scheduled`, `onChange` or `fetch`). Every write from a browser or the CLI arrives with `backend` absent, the app owner's own writes included — it says where a write came from, never who the writer is. Gate on `ctx.requireBackend(...)` rather than throwing on `user.backend` yourself: a bare `throw` on `user.backend` refuses the hand-repair lane admin mode exists for, and returns no descriptor for the write it refused.
26
26
 
27
- **Helpers (`ctx`):** Opaque closures over the materialized grant state. They throw or pass — you cannot enumerate channels, list members, or iterate grants. Both helpers also throw when `user` is null: `ctx.requireAccess(channelId)` throws if user is not in the channel, `ctx.requireRole(roleName)` throws if user is not in the role.
27
+ **Helpers (`ctx`):** Opaque closures over the materialized grant state. They throw or pass — you cannot enumerate channels, list members, or iterate grants. They also throw when `user` is null: `ctx.requireAccess(channelId)` throws if user is not in the channel, `ctx.requireRole(roleName)` throws if user is not in the role, and `ctx.requireBackend()` throws unless the app's own `backend.js` issued this write — `ctx.requireBackend("scheduled")` narrows that to one handler. All three no-op under admin mode.
28
+
29
+ Express every refusal about **who may act** through the supplied validators — `ctx.requireAccess`, `ctx.requireRole`, `ctx.requireBackend` — so that admin mode passes them and the function still runs, applying your own channels and grants to the repaired document; a refusal about the document's own **shape**, like an unknown type or a missing field, stays a plain `throw` and is right to, while a write-once check sits in between and is worth naming in a comment, since a schema repair has to get past it.
28
30
 
29
31
  The access function's scope is the app's own databases: a platform component's own database (a `media:*` name) carries platform-authored access rules the server evaluates separately, so your access function writes rules only for the databases the app itself creates. Within those, platform-driven writes onto the app's own docs — like an `<ImgGen>` version append (the `ctx.isImgGenVersionAppend` shape) — flow through your function like any other write.
30
32
 
@@ -189,6 +191,25 @@ This is especially useful when an app has many databases.
189
191
 
190
192
  Use `oldDoc` (the previous version of the document) to enforce invariants across updates: `if (oldDoc === null) { /* create-only logic */ }` for new documents, and `if (oldDoc && doc.version <= oldDoc.version) { throw { forbidden: "version must increase" } }` for monotonic versions.
191
193
 
194
+ ### Documents only the app's own backend writes
195
+
196
+ Some documents belong to the machinery rather than to a person: a scheduled run's state doc, a counter a `scheduled` sweep advances, a notification the backend generates. Gate those on the **origin** with `ctx.requireBackend(...)` — one line, no conditional:
197
+
198
+ ```js
199
+ if (doc.type === "runState") {
200
+ ctx.requireBackend("scheduled");
201
+ return { channels: ["show"] };
202
+ }
203
+ ```
204
+
205
+ The owner-handle version of this rule looks equivalent and is not. The person holding the owner handle sits in front of the app in a browser, so a rule that admits them admits a hand-written `runState` doc typed from the UI — it keeps strangers out of the run's memory and lets in the one person most likely to be poking at it. `ctx.requireBackend("scheduled")` admits the scheduled run and turns every ordinary client write away.
206
+
207
+ This is not a wall against the app's own maintainer. `ctx.requireBackend` no-ops under admin mode, the same way `ctx.requireAccess` and `ctx.requireRole` do, so the owner or platform support repairing a document by hand passes the gate — and the function still runs, so the repaired doc gets the app's own channels and grants and lands routed. That is the point: a document nobody can hand-fix is a support dead end.
208
+
209
+ Name the handler. `ctx.requireBackend()` with no argument admits any of the three, and one of them is `fetch` — a route any visitor can call. A bare `ctx.requireBackend()` on a run's state doc therefore reads as "only my machinery" while admitting anyone who can reach your `_api` endpoint, so name the handler you mean unless you genuinely accept all three.
210
+
211
+ One trap: applying a release's `seed.json` is the **platform** writing, not the app's `backend.js`, so a seeded document arrives with `backend` absent and `ctx.requireBackend` turns it away. Seed that type under a permissive rule, then tighten it once the app is running — or leave the machinery's own documents out of the seed entirely, which is usually what they want.
212
+
192
213
  ## When to emit access.js, and where it goes
193
214
 
194
215
  The **placement** of `access.js` depends on which turn shape you are in — one-shot whole-app generation vs. incremental scaffold/follow-up edits. Follow the subsection that matches your turn; the other one's ordering is wrong for you and will strand writes.
@@ -859,7 +880,7 @@ const addCard = (text) => database.put({ type: "card", boardId: pickedBoardId ||
859
880
  {canInvite(board._id) && <HandleInput onChange={(h) => addMember(board._id, h)} placeholder="Add a friend…" />}
860
881
  ```
861
882
 
862
- Where the app's own maintainer needs to move records through the gate — a CLI migration re-homing cards — `!user.isOwner` reads as a bypass written beside a check (`if (!user.isOwner) ctx.requireAccess(chan)`), never as the arm that decides who may act.
883
+ A new access function has no use for `user.isOwner` at all: a rule about a **person** keys on `user.userHandle` or the object's own creator field, and a rule about the app's **own automation** keys on `user.backend`.
863
884
 
864
885
  #### A person the ask names is let in by a record
865
886
 
package/llms/backend.md CHANGED
@@ -71,19 +71,23 @@ allowed by it for the acting identity. If a `fetch` handler writes, make sure
71
71
  the access function permits that write for an anonymous caller (or design the
72
72
  write to happen in `onChange`/`scheduled`, which carry stronger identities).
73
73
 
74
- The flip side: the access function **cannot tell** a backend write from a user
75
- write — that's the invariant. An `onChange` write acts as the triggering user,
76
- so anything it may write, that user's own client could write too. `onChange` is
77
- for **derivation and convenience**, never privilege escalation. For documents
78
- only the server should control, use `scheduled` — it acts as the **owner**, an
79
- identity `access.js` can genuinely restrict a database to.
74
+ The access function can tell where a write came from: `user.backend` names the
75
+ handler that issued it — `"scheduled"`, `"onChange"` or `"fetch"` — and is
76
+ absent for every write from a browser or the CLI, the app owner's own included.
77
+ What it does **not** change is identity. An `onChange` write still acts as the
78
+ triggering user, so anything it may write, that user's own client could write
79
+ too; `onChange` stays **derivation and convenience**, never privilege
80
+ escalation. For documents only the server should control, use `scheduled` and
81
+ gate them on the origin with `ctx.requireBackend("scheduled")`, which admits the
82
+ run and turns every ordinary client write away.
80
83
 
81
84
  `scheduled` always runs in **admin mode**, the same override the owner gets
82
- from the client's admin toggle: `ctx.requireAccess(...)`/`ctx.requireRole(...)`
83
- no-op, and its `ctx.db.query` reads are unfiltered. The access function still
84
- **runs** on every write — its returned channels and grants still route the doc,
85
- so members keep seeing what the cron writes. `user.isOwner` is `true`, so
86
- owner-conditional rules apply as the owner. Two consequences worth designing
85
+ from the client's admin toggle: `ctx.requireAccess(...)`, `ctx.requireRole(...)`
86
+ and `ctx.requireBackend(...)` all no-op, and its `ctx.db.query` reads are
87
+ unfiltered. The access function still **runs** on every write — its returned
88
+ channels and grants still route the doc, so members keep seeing what the cron
89
+ writes. `user.backend` is `"scheduled"`, so `ctx.requireBackend("scheduled")`
90
+ passes for the run on its own merits. Two consequences worth designing
87
91
  around: a scheduled sweep can read and rewrite **every user's** documents in
88
92
  the app (access functions protect users from *each other*, not from the app
89
93
  owner — the owner's automation touches all users' data); and because admin mode
@@ -991,15 +995,18 @@ export async function scheduled(event, ctx) {
991
995
  }
992
996
  ```
993
997
 
994
- The run's own bookkeeping is the server's, not a user's. `scheduled` acts as the
995
- app owner, so `user.isOwner` is the one condition that separates a run's write
996
- from a client pretending to be one — and `access.js` is where that is said:
998
+ The run's own bookkeeping is the server's, not a user's. `ctx.requireBackend`
999
+ is what separates a run's write from a client's: it passes only when the app's
1000
+ own `backend.js` issued the write, and refuses every browser and CLI write
1001
+ including the owner's own — while still no-opping under admin mode, so a hand
1002
+ repair runs the function and keeps the routing. `access.js` is where that is
1003
+ said:
997
1004
 
998
1005
  ```js
999
1006
  // access.js
1000
- export default function ({ doc, user }) {
1007
+ export default function (doc, oldDoc, user, ctx) {
1001
1008
  if (doc.type === "runState" || doc.type === "runStatus") {
1002
- if (!user?.isOwner) throw { forbidden: "the run writes its own state" };
1009
+ ctx.requireBackend("scheduled");
1003
1010
  return { channels: ["show"] };
1004
1011
  }
1005
1012
  // …the app's own document types…
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibes.diy/prompts",
3
- "version": "14.1.33",
3
+ "version": "14.1.35",
4
4
  "type": "module",
5
5
  "main": "./index.js",
6
6
  "exports": {
@@ -34,9 +34,9 @@
34
34
  "license": "Apache-2.0",
35
35
  "dependencies": {
36
36
  "@adviser/cement": "~0.5.34",
37
- "@vibes.diy/call-ai-v2": "14.1.33",
38
- "@vibes.diy/identity": "14.1.33",
39
- "@vibes.diy/use-vibes-types": "14.1.33",
37
+ "@vibes.diy/call-ai-v2": "14.1.35",
38
+ "@vibes.diy/identity": "14.1.35",
39
+ "@vibes.diy/use-vibes-types": "14.1.35",
40
40
  "arktype": "~2.2.3",
41
41
  "json-schema-faker": "~0.6.3"
42
42
  },