@vibes.diy/prompts 2.5.14 → 2.5.15
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/fireproof.md +36 -8
- package/package.json +4 -4
- package/system-prompt-initial.md +2 -1
- package/system-prompt.md +3 -1
package/llms/fireproof.md
CHANGED
|
@@ -420,11 +420,22 @@ export function announcements(doc, oldDoc, user, ctx) {
|
|
|
420
420
|
|
|
421
421
|
if (doc.type === "roleGrant") {
|
|
422
422
|
if (!user.isOwner) throw { forbidden: "owner only" };
|
|
423
|
-
|
|
423
|
+
// A grant doc must ALSO route to a channel — a result with no `channels`
|
|
424
|
+
// (only `members`/`grant`) is rejected as an "unreadable write". Route it to
|
|
425
|
+
// an owner-readable admin channel (not a public one) so the grant persists
|
|
426
|
+
// and the owner can read the roster back; the membership then applies.
|
|
427
|
+
return {
|
|
428
|
+
channels: ["admin:grants"],
|
|
429
|
+
members: { [doc.role]: [doc.userHandle] },
|
|
430
|
+
grant: { users: { [user.userHandle]: ["admin:grants"] } },
|
|
431
|
+
};
|
|
424
432
|
}
|
|
425
433
|
|
|
426
434
|
if (doc.type === "post") {
|
|
427
435
|
if (doc.authorHandle !== user.userHandle) throw { forbidden: "not author" };
|
|
436
|
+
// On update, the original author must be preserved — never let a writer
|
|
437
|
+
// overwrite someone else's doc or reassign its author.
|
|
438
|
+
if (oldDoc && oldDoc.authorHandle !== user.userHandle) throw { forbidden: "not author" };
|
|
428
439
|
ctx.requireAccess(doc.channel);
|
|
429
440
|
return { channels: [doc.channel] };
|
|
430
441
|
}
|
|
@@ -529,6 +540,7 @@ export function chat(doc, oldDoc, user, ctx) {
|
|
|
529
540
|
|
|
530
541
|
if (doc.type === "post") {
|
|
531
542
|
if (doc.authorHandle !== user.userHandle) throw { forbidden: "not author" };
|
|
543
|
+
if (oldDoc && oldDoc.authorHandle !== user.userHandle) throw { forbidden: "not author" };
|
|
532
544
|
// Any signed-in author may post to this open channel. Do NOT call
|
|
533
545
|
// ctx.requireAccess(doc.channel) here: the channel is grant.public
|
|
534
546
|
// (read-only), which never satisfies requireAccess, so gating on it would
|
|
@@ -589,12 +601,16 @@ Access functions live in `/access.js`, a separate file in the vibe's filesystem
|
|
|
589
601
|
|
|
590
602
|
**`requireAccess` checks _membership_, not public read — don't gate an open channel's writes on it.** `ctx.requireAccess(channelId)` passes only for a channel the user is a member of: granted directly through `grant.users[handle]`, or through a `grant.roles` role they hold. **`grant.public` does NOT satisfy `requireAccess`** — public is read-only ("anyone through the door can _read_"), it never confers write membership. So a channel that is only `grant.public` and gated on `ctx.requireAccess` can be written by **nobody** but the owner-in-admin-mode — every other write returns `not in channel`, silently hiding the form (`useVibe().can` faithfully reflects this). Choose by intent:
|
|
591
603
|
|
|
592
|
-
- **Open channel — any signed-in user may post** (public board, guestbook, comment wall): do **not** call `ctx.requireAccess`. Route the doc to the channel and check the author — `if (doc.authorHandle !== user.userHandle) throw { forbidden: "not author" }; return { channels: [doc.channelId] }`. `grant.public` on the channel doc gives everyone read; the write is open to any author.
|
|
604
|
+
- **Open channel — any signed-in user may post** (public board, guestbook, comment wall): do **not** call `ctx.requireAccess`. Route the doc to the channel and check the author — `if (doc.authorHandle !== user.userHandle) throw { forbidden: "not author" }; if (oldDoc && oldDoc.authorHandle !== user.userHandle) throw { forbidden: "not author" }; return { channels: [doc.channelId] }`. `grant.public` on the channel doc gives everyone read; the write is open to any author.
|
|
593
605
|
- **Restricted channel — only members may post**: gate the write on `ctx.requireAccess(doc.channelId)` **and** grant writers membership explicitly — `grant.users` (direct) or `grant.roles` + a `members`/role-grant doc. `public` alongside is read-only and is fine for letting non-members read, but it is never what lets a member write.
|
|
594
606
|
|
|
595
607
|
### AccessDescriptor return type
|
|
596
608
|
|
|
597
|
-
All fields are optional, but a stored document must be routed to at least one channel (`channels`) to be readable — a result with no channels is refused at write time. To reject a write outright, `throw { forbidden: "reason" }`.
|
|
609
|
+
All fields are optional, but a stored document must be routed to at least one channel (`channels`) to be readable — a result with no channels is refused at write time ("unreadable write"). To reject a write outright, `throw { forbidden: "reason" }`.
|
|
610
|
+
|
|
611
|
+
**Grant/member/meta docs need a channel too.** A role grant, membership, or config singleton that returns only `members`/`grant` with **no `channels`** is refused exactly like any other channel-less write — so the owner can't even create it. Route these to an owner-readable **admin channel** (e.g. `channels: ["admin:grants"]` with `grant: { users: { [user.userHandle]: ["admin:grants"] } }`), not a public channel. The `members`/`grant` still take effect globally; the channel just makes the doc persist and lets the owner read the roster back.
|
|
612
|
+
|
|
613
|
+
**Preserve the author on updates.** For an author-owned doc, checking the new author field is not enough — also check `oldDoc` so a writer can't overwrite someone else's doc or reassign its author: `if (oldDoc && oldDoc.<authorField> !== user.userHandle) throw { forbidden: "not author" }`, where `<authorField>` is whatever your doc uses (`authorHandle`, `userHandle`, `senderHandle`, …). (Write-once docs can simply `if (oldDoc) throw`.)
|
|
598
614
|
|
|
599
615
|
```ts
|
|
600
616
|
type AccessDescriptor = {
|
|
@@ -654,12 +670,14 @@ export function chat(doc, oldDoc, user, ctx) {
|
|
|
654
670
|
|
|
655
671
|
if (doc.type === "message") {
|
|
656
672
|
if (doc.userHandle !== user.userHandle) throw { forbidden: "not author" };
|
|
673
|
+
if (oldDoc && oldDoc.userHandle !== user.userHandle) throw { forbidden: "not author" };
|
|
657
674
|
ctx.requireAccess(doc.channelId);
|
|
658
675
|
return { channels: [doc.channelId] };
|
|
659
676
|
}
|
|
660
677
|
|
|
661
678
|
if (doc.type === "channel-invite") {
|
|
662
679
|
if (doc.senderHandle !== user.userHandle) throw { forbidden: "not sender" };
|
|
680
|
+
if (oldDoc && oldDoc.senderHandle !== user.userHandle) throw { forbidden: "not sender" };
|
|
663
681
|
ctx.requireAccess(doc.channelId);
|
|
664
682
|
return {
|
|
665
683
|
channels: [doc.channelId],
|
|
@@ -686,11 +704,13 @@ export function survey(doc, oldDoc, user, ctx) {
|
|
|
686
704
|
|
|
687
705
|
if (doc.type === "survey-config") {
|
|
688
706
|
if (!user.isOwner) throw { forbidden: "owner only" };
|
|
707
|
+
// Route this grant/config doc to an owner-readable admin channel — a
|
|
708
|
+
// grant-only result (no `channels`) is rejected as an "unreadable write".
|
|
689
709
|
return {
|
|
710
|
+
channels: ["admin:grants"],
|
|
690
711
|
grant: {
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
},
|
|
712
|
+
users: { [user.userHandle]: ["admin:grants"] },
|
|
713
|
+
roles: { "feedback-team": ["inbound-responses"] },
|
|
694
714
|
},
|
|
695
715
|
};
|
|
696
716
|
}
|
|
@@ -801,16 +821,24 @@ export function chat(doc, oldDoc, user, ctx) {
|
|
|
801
821
|
export function chat(doc, oldDoc, user, ctx) {
|
|
802
822
|
if (!user) throw { forbidden: "authentication required" };
|
|
803
823
|
|
|
824
|
+
// Grant/meta docs must also route to a channel — a channel-less result is
|
|
825
|
+
// rejected as "unreadable write". Route them to an owner-readable admin channel.
|
|
804
826
|
if (doc.type === "team-meta") {
|
|
805
827
|
if (!user.isOwner) throw { forbidden: "owner only" };
|
|
806
828
|
return {
|
|
829
|
+
channels: ["admin:grants"],
|
|
807
830
|
members: { [doc.teamId]: doc.memberHandles },
|
|
808
|
-
grant: { roles: { [doc.teamId]: doc.channels } },
|
|
831
|
+
grant: { users: { [user.userHandle]: ["admin:grants"] }, roles: { [doc.teamId]: doc.channels } },
|
|
809
832
|
};
|
|
810
833
|
}
|
|
811
834
|
|
|
812
835
|
if (doc.type === "membership") {
|
|
813
|
-
|
|
836
|
+
if (!user.isOwner) throw { forbidden: "owner only" };
|
|
837
|
+
return {
|
|
838
|
+
channels: ["admin:grants"],
|
|
839
|
+
members: { [doc.role]: [doc.userHandle] },
|
|
840
|
+
grant: { users: { [user.userHandle]: ["admin:grants"] } },
|
|
841
|
+
};
|
|
814
842
|
}
|
|
815
843
|
|
|
816
844
|
ctx.requireAccess(doc.channelId);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vibes.diy/prompts",
|
|
3
|
-
"version": "2.5.
|
|
3
|
+
"version": "2.5.15",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./index.js",
|
|
6
6
|
"description": "",
|
|
@@ -30,9 +30,9 @@
|
|
|
30
30
|
"@fireproof/core-types-base": "~0.24.19",
|
|
31
31
|
"@fireproof/core-types-protocols-cloud": "~0.24.19",
|
|
32
32
|
"@fireproof/use-fireproof": "~0.24.19",
|
|
33
|
-
"@vibes.diy/call-ai-v2": "^2.5.
|
|
34
|
-
"@vibes.diy/identity": "^2.5.
|
|
35
|
-
"@vibes.diy/use-vibes-types": "^2.5.
|
|
33
|
+
"@vibes.diy/call-ai-v2": "^2.5.15",
|
|
34
|
+
"@vibes.diy/identity": "^2.5.15",
|
|
35
|
+
"@vibes.diy/use-vibes-types": "^2.5.15",
|
|
36
36
|
"arktype": "~2.2.1",
|
|
37
37
|
"json-schema-faker": "~0.6.2"
|
|
38
38
|
},
|
package/system-prompt-initial.md
CHANGED
|
@@ -68,13 +68,14 @@ Target ~40–60 lines. The shell should look like a real app with empty sections
|
|
|
68
68
|
> }
|
|
69
69
|
> if (doc.type === "message") {
|
|
70
70
|
> if (doc.authorHandle !== user.userHandle) throw { forbidden: "not author" };
|
|
71
|
+
> if (oldDoc && oldDoc.authorHandle !== user.userHandle) throw { forbidden: "not author" };
|
|
71
72
|
> return { channels: [doc.channelId] };
|
|
72
73
|
> }
|
|
73
74
|
> throw { forbidden: "unknown document type" };
|
|
74
75
|
> }
|
|
75
76
|
> ```
|
|
76
77
|
|
|
77
|
-
`ctx.requireAccess(channel)` gates on **membership** (a `grant.users`/`grant.roles` grant), not `grant.public` (read-only) — so an open channel anyone signed-in may post to must not gate writes on it; check the author and route the doc. For writes needing no sign-in ("anyone can sign/submit"), return `allowAnonymous: true` instead of throwing on `!user`.
|
|
78
|
+
`ctx.requireAccess(channel)` gates on **membership** (a `grant.users`/`grant.roles` grant), not `grant.public` (read-only) — so an open channel anyone signed-in may post to must not gate writes on it; check the author and route the doc. For writes needing no sign-in ("anyone can sign/submit"), return `allowAnonymous: true` instead of throwing on `!user`. A grant/member doc must also return `channels` (route it to an owner-readable admin channel) — a channel-less result is rejected. On updates, also check the old author field (`oldDoc.authorHandle`/`userHandle`/`senderHandle`) so a writer can't overwrite or re-author someone else's doc.
|
|
78
79
|
|
|
79
80
|
**Never put access function code inside an `App.jsx` block** — it will overwrite the React component. The filename line (`access.js` vs `App.jsx`) is how the system knows which file to write.
|
|
80
81
|
|
package/system-prompt.md
CHANGED
|
@@ -275,6 +275,7 @@ When the app uses channel-based read isolation or per-document write validation,
|
|
|
275
275
|
> if (!user) throw { forbidden: "authentication required" };
|
|
276
276
|
> if (doc.type === "message") {
|
|
277
277
|
> if (doc.userHandle !== user.userHandle) throw { forbidden: "not author" };
|
|
278
|
+
> if (oldDoc && oldDoc.userHandle !== user.userHandle) throw { forbidden: "not author" };
|
|
278
279
|
> ctx.requireAccess(doc.channelId);
|
|
279
280
|
> return { channels: [doc.channelId] };
|
|
280
281
|
> }
|
|
@@ -282,7 +283,7 @@ When the app uses channel-based read isolation or per-document write validation,
|
|
|
282
283
|
> }
|
|
283
284
|
> ```
|
|
284
285
|
|
|
285
|
-
`ctx.requireAccess(channel)` gates on channel **membership** (a `grant.users`/`grant.roles` grant), NOT on `grant.public`, which is read-only — so a channel anyone signed-in should post to must **not** gate writes on `requireAccess` (it would block every non-owner); just check the author and route the doc. Reserve `requireAccess` for members-only channels whose writers you granted membership. For writes that need no sign-in at all ("anyone can sign/submit"), return `allowAnonymous: true` instead of throwing on `!user`. See the fireproof access docs.
|
|
286
|
+
`ctx.requireAccess(channel)` gates on channel **membership** (a `grant.users`/`grant.roles` grant), NOT on `grant.public`, which is read-only — so a channel anyone signed-in should post to must **not** gate writes on `requireAccess` (it would block every non-owner); just check the author and route the doc. Reserve `requireAccess` for members-only channels whose writers you granted membership. For writes that need no sign-in at all ("anyone can sign/submit"), return `allowAnonymous: true` instead of throwing on `!user`. A grant/member/role doc must also return `channels` (route it to an owner-readable admin channel like `["admin:grants"]`) — a channel-less result is rejected as "unreadable write". On updates, also check `oldDoc` (`if (oldDoc && oldDoc.<authorField> !== user.userHandle) throw`, where `<authorField>` is your doc's author field — `authorHandle`/`userHandle`/`senderHandle`) so a writer can't overwrite or re-author someone else's doc. See the fireproof access docs.
|
|
286
287
|
|
|
287
288
|
**Never put access function code inside an `App.jsx` block** — it will overwrite the React component. The filename line (`access.js` vs `App.jsx`) is how the system knows which file to write.
|
|
288
289
|
|
|
@@ -417,6 +418,7 @@ Example streamed output for a team board app:
|
|
|
417
418
|
>
|
|
418
419
|
> if (doc.type === "post") {
|
|
419
420
|
> if (doc.authorHandle !== user.userHandle) throw { forbidden: "not author" };
|
|
421
|
+
> if (oldDoc && oldDoc.authorHandle !== user.userHandle) throw { forbidden: "not author" };
|
|
420
422
|
> return { channels: [doc.channelId] };
|
|
421
423
|
> }
|
|
422
424
|
>
|