pi-simocracy 0.5.1 → 0.6.1

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.
@@ -0,0 +1,198 @@
1
+ # Sim-authored proposals
2
+
3
+ How pi-simocracy attributes a funding proposal to a sim *without*
4
+ extending the `org.hypercerts.claim.activity` lexicon — and what
5
+ [simocracy-v2](https://github.com/GainForest/simocracy-v2) needs to
6
+ do to render the attribution. Same pattern as
7
+ [sim-authored comments](./SIM_AUTHORED_COMMENTS.md), different
8
+ subject collection.
9
+
10
+ ---
11
+
12
+ ## TL;DR
13
+
14
+ When pi submits a proposal on behalf of a loaded sim, it writes
15
+ **two** records to the user's PDS:
16
+
17
+ 1. **`org.hypercerts.claim.activity`** — the proposal itself, in the
18
+ exact shape simocracy.org's `ProposalFormDialog` already writes
19
+ today (`title`, `shortDescription`, optional `description` /
20
+ `workScope` / `contributors` / `image`, `createdAt`). Old readers
21
+ (Hyperindexer, the existing webapp) see this as a regular
22
+ user-authored proposal — graceful degradation.
23
+ 2. **`org.simocracy.history`** — sidecar record with `type:
24
+ "proposal"`, `subjectUri` pointing at the proposal we just wrote,
25
+ `simUris[]` / `simNames[]` declaring which sim spoke. New `type`
26
+ value, but the lexicon's `type` field is free-form string and the
27
+ indexer already accepts new event types as they appear.
28
+
29
+ Renderers that understand the join (simocracy.org, when the planned
30
+ change lands) will display a sim badge on the proposal card;
31
+ renderers that don't keep showing it as a regular user proposal.
32
+ **Zero hypercerts lexicon changes. Zero new Simocracy lexicons.**
33
+
34
+ ---
35
+
36
+ ## Why no lexicon change
37
+
38
+ The `org.hypercerts.*` namespace is owned by the GainForest
39
+ hyperindexer project, not Simocracy — extending
40
+ `org.hypercerts.claim.activity` with a `sim` StrongRef field would
41
+ couple two independent release cycles together for one cross-app
42
+ feature, and the same argument made for comments
43
+ ([`SIM_AUTHORED_COMMENTS.md`](./SIM_AUTHORED_COMMENTS.md)) applies
44
+ verbatim here.
45
+
46
+ The `org.simocracy.history` lexicon already has every field we need:
47
+
48
+ | Field | Used for sim-authored proposals |
49
+ |--------------------|--------------------------------------------------------------|
50
+ | `type` | `"proposal"` (new value — appended like other event types) |
51
+ | `actorDid` | The human who submitted on the sim's behalf |
52
+ | `simNames[]` | Display name(s) of the sim(s) credited as author |
53
+ | `simUris[]` | AT-URI(s) of the sim(s) — the sim-attribution key |
54
+ | `subjectUri` | AT-URI of the proposal record this attribution applies to |
55
+ | `subjectCollection`| `"org.hypercerts.claim.activity"` |
56
+ | `subjectName` | Proposal title (denormalized for the timeline) |
57
+ | `proposalTitle` | Same as `subjectName` — kept parallel to comment sidecars |
58
+ | `content` | Denormalized description (so the indexer doesn't have to join across PDSs to display the timeline) |
59
+ | `createdAt` | ISO timestamp |
60
+
61
+ Use it as-is.
62
+
63
+ ---
64
+
65
+ ## Write path (pi-simocracy)
66
+
67
+ Implemented by `simocracy_post_proposal` in `src/index.ts`:
68
+
69
+ ```ts
70
+ // 1. The proposal — same shape as ProposalFormDialog writes today.
71
+ const proposal = await createProposal({
72
+ agent, did,
73
+ title,
74
+ shortDescription,
75
+ description: finalDescription, // user body + appended budget block, if any
76
+ workScope,
77
+ contributors: contributors.map((c) => ({ contributorIdentity: c })),
78
+ image: { $type: "org.hypercerts.defs#uri", uri: imageUri ?? DEFAULT_BANNER },
79
+ });
80
+
81
+ // 2. The sim-attribution sidecar — required, since attribution is the whole
82
+ // point of this tool.
83
+ await createProposalHistory({
84
+ agent, did,
85
+ proposalUri: proposal.uri,
86
+ proposalTitle: title,
87
+ simUri: loadedSim.uri,
88
+ simName: loadedSim.name,
89
+ content: finalDescription || shortDescription,
90
+ });
91
+ ```
92
+
93
+ Both writes go to the **user's** PDS via their OAuth session — same
94
+ auth path that already powers `simocracy_post_comment` and
95
+ `simocracy_update_sim`. The write is gated on `/sim login` plus sim
96
+ ownership (the sim must live in the signed-in DID's repo) — the
97
+ sidecar uses `assertRepoOwnsSimUri` as defense-in-depth, identical
98
+ to the comment path.
99
+
100
+ If the sidecar write fails after the proposal succeeds, **the
101
+ proposal is not rolled back** — it just shows up unattributed until
102
+ the user retries. We don't roll back, because rolling back leaves an
103
+ orphaned tombstone in the user's repo that's harder to reason about
104
+ than a missing badge. The tool surfaces a `sidecarWarning` in the
105
+ result so the LLM can decide whether to retry.
106
+
107
+ ### What we deliberately don't do
108
+
109
+ - **Image upload from disk.** The webapp uploads to `/api/upload-blob`;
110
+ pi-simocracy has no equivalent and we deliberately stay
111
+ read-mostly on blobs. The tool accepts an https `imageUri` only,
112
+ with a default that mirrors the webapp's banner fallback.
113
+ - **Adding the proposal to a floor / collection.** That's a separate
114
+ `org.hypercerts.claim.collection` write the webapp does via
115
+ `/api/ftc-sf/add-to-collection`. Out of scope for this tool; a
116
+ follow-up tool can chain it after the fact.
117
+ - **Editing existing proposals.** Create-only for now.
118
+
119
+ ---
120
+
121
+ ## Read path (proposed simocracy-v2 changes)
122
+
123
+ Mirrors the comment renderer change in shape, applied to the
124
+ proposal list / detail views.
125
+
126
+ ### 1. Proposal list query — pull history records in parallel
127
+
128
+ After fetching the proposals for a floor / collection, fetch all
129
+ `org.simocracy.history` records (capped, like notifications does)
130
+ and build a `Map<proposalUri, HistoryRecord>` keyed on `subjectUri`.
131
+ Filter to `type === "proposal"` and `subjectCollection ===
132
+ "org.hypercerts.claim.activity"`. Attach `simUri`, `simName`, and a
133
+ resolved `simAvatarUrl` to each proposal in the response that has a
134
+ match. Sim avatar resolution can reuse `fetchAllSimsWithMeta()` —
135
+ no extra round-trips per proposal.
136
+
137
+ ### 2. Extend the proposal type
138
+
139
+ ```ts
140
+ export interface ProposalRecord {
141
+ // …existing fields…
142
+ simUri?: string
143
+ simName?: string
144
+ simAvatarUrl?: string
145
+ }
146
+ ```
147
+
148
+ No change to `ProposalFormDialog` — that path stays for human
149
+ authors. Sim-authored proposals come from the CLI today, and a
150
+ future "submit as sim" button in the modal would bundle both writes
151
+ the same way pi-simocracy does.
152
+
153
+ ### 3. Proposal card — sim badge
154
+
155
+ In the proposal card component, when `proposal.simUri` is set:
156
+
157
+ - Replace the author avatar with the sim's sprite (32×32 walk-1 frame).
158
+ - Render the byline as `🐾 {simName} · drafted by @{userHandle}` so
159
+ attribution stays unambiguous (the sim "drafted" it; the human
160
+ submitted and owns the record).
161
+ - Add a `[sim]` mono-uppercase badge alongside the existing meta
162
+ pills.
163
+ - Link the sim name to `/sims/{did}/{rkey}` via the existing slug
164
+ resolver.
165
+
166
+ Proposals without `simUri` keep rendering exactly as today — no
167
+ regression for human-authored proposals.
168
+
169
+ ---
170
+
171
+ ## Querying sim-authored proposals
172
+
173
+ For "show me everything my sim has proposed":
174
+
175
+ ```ts
176
+ const histories = await fetchHistory() // existing helper
177
+ const mySimProposals = histories.filter(h =>
178
+ h.event.type === "proposal" &&
179
+ h.event.simUris?.includes(mySimUri)
180
+ )
181
+ ```
182
+
183
+ Each result has `subjectUri` (the proposal URI) and `content`
184
+ (denormalized description). Resolve the proposal URI for the full
185
+ record. Same query shape the notifications system already uses for
186
+ chat / hearing / sprocess events — no new indexer queries needed.
187
+
188
+ ---
189
+
190
+ ## Status
191
+
192
+ - ✅ Implemented in pi-simocracy `simocracy_post_proposal` (this repo)
193
+ - 🟡 Renderer changes pending in simocracy-v2
194
+ - 🟢 Lexicons unchanged — both repos can ship the change independently
195
+
196
+ The proposal + history pair is being written today. As soon as
197
+ simocracy-v2 lands the renderer change, every existing pi-authored
198
+ sim proposal retro-actively gets the sim badge — no migration.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-simocracy",
3
- "version": "0.5.1",
3
+ "version": "0.6.1",
4
4
  "description": "Pi extension: load a Simocracy sim into your chat — see its pixel-art sprite render inline in the terminal and roleplay with it.",
5
5
  "type": "module",
6
6
  "author": "David Dao <david@gainforest.earth> (https://github.com/daviddao)",
@@ -32,6 +32,7 @@
32
32
  },
33
33
  "files": [
34
34
  "src/",
35
+ "docs/",
35
36
  "README.md",
36
37
  "LICENSE"
37
38
  ],