@augurworks/augur 0.15.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.
Files changed (190) hide show
  1. package/CANVAS.md +858 -0
  2. package/INSTALL.md +603 -0
  3. package/LICENSE +27 -0
  4. package/README.md +150 -0
  5. package/TRADEMARKS.md +72 -0
  6. package/agents/README.md +65 -0
  7. package/agents/canon-extract.md +117 -0
  8. package/agents/canon.md +151 -0
  9. package/agents/canvas.md +88 -0
  10. package/agents/currency.md +74 -0
  11. package/agents/drafts.md +92 -0
  12. package/agents/identity.md +148 -0
  13. package/agents/prototype-contract.md +69 -0
  14. package/agents/publishing.md +324 -0
  15. package/agents/review-feedback.md +76 -0
  16. package/agents/space-json.md +132 -0
  17. package/agents/ui-skill.md +101 -0
  18. package/agents/working-marks.md +86 -0
  19. package/brand/augur-eye.svg +3 -0
  20. package/brand/augur-icon.svg +41 -0
  21. package/brand/augur-mark.svg +5 -0
  22. package/brand/lentianova-bulky.otf +0 -0
  23. package/brand/lentianova-median.otf +0 -0
  24. package/brand/lentianova.woff2 +0 -0
  25. package/build.js +8409 -0
  26. package/changelog.md +133 -0
  27. package/fonts/inter-latin-wght-normal.woff2 +0 -0
  28. package/package.json +57 -0
  29. package/pitis/PETS.md +121 -0
  30. package/pitis/README.md +117 -0
  31. package/pitis/index.html +266 -0
  32. package/pitis/piti.build.js +81 -0
  33. package/pitis/piti.js +929 -0
  34. package/pitis/roast-agent.md +185 -0
  35. package/realtime/src/index.js +119 -0
  36. package/realtime/wrangler.example.toml +44 -0
  37. package/scripts/adopt.mjs +106 -0
  38. package/scripts/board-snapshot.mjs +197 -0
  39. package/scripts/build-seed-pack.mjs +33 -0
  40. package/scripts/bundle-rekey.mjs +112 -0
  41. package/scripts/bundle-tenancy-rehearsal.mjs +781 -0
  42. package/scripts/canon-extract.mjs +578 -0
  43. package/scripts/canon.mjs +599 -0
  44. package/scripts/canvas-screen.mjs +333 -0
  45. package/scripts/check-local.mjs +136 -0
  46. package/scripts/clawd-art.mjs +75 -0
  47. package/scripts/clawd-canvas.mjs +741 -0
  48. package/scripts/cli.mjs +100 -0
  49. package/scripts/clone.mjs +422 -0
  50. package/scripts/close.mjs +30 -0
  51. package/scripts/connect.mjs +103 -0
  52. package/scripts/deploy.mjs +157 -0
  53. package/scripts/dev.mjs +54 -0
  54. package/scripts/doc-lint.mjs +143 -0
  55. package/scripts/export.mjs +300 -0
  56. package/scripts/fork.mjs +64 -0
  57. package/scripts/freeze.mjs +74 -0
  58. package/scripts/frontdoor-parity.mjs +180 -0
  59. package/scripts/hook.mjs +67 -0
  60. package/scripts/identity-rekey.mjs +112 -0
  61. package/scripts/init.mjs +122 -0
  62. package/scripts/land.mjs +40 -0
  63. package/scripts/lib/adapters.mjs +156 -0
  64. package/scripts/lib/board-snapshot.mjs +511 -0
  65. package/scripts/lib/draft.mjs +385 -0
  66. package/scripts/lib/graduate.mjs +276 -0
  67. package/scripts/lib/instance.mjs +74 -0
  68. package/scripts/lib/marks.mjs +107 -0
  69. package/scripts/lib/materialize.mjs +138 -0
  70. package/scripts/lib/merge3.mjs +124 -0
  71. package/scripts/lib/offline-posture.mjs +120 -0
  72. package/scripts/lib/offline-respawn.mjs +18 -0
  73. package/scripts/lib/offline-wrangler.mjs +78 -0
  74. package/scripts/lib/publish-compose.mjs +12 -0
  75. package/scripts/lib/publish-conflict.mjs +78 -0
  76. package/scripts/lib/publish-evidence.mjs +144 -0
  77. package/scripts/lib/refine-compare.mjs +140 -0
  78. package/scripts/lib/refine-ledger.mjs +150 -0
  79. package/scripts/lib/refine-png.mjs +149 -0
  80. package/scripts/lib/seed-pack-build.mjs +141 -0
  81. package/scripts/lib/state-compare.mjs +68 -0
  82. package/scripts/lib/store.mjs +122 -0
  83. package/scripts/login.mjs +84 -0
  84. package/scripts/mark.mjs +112 -0
  85. package/scripts/migrate-board-keys.mjs +164 -0
  86. package/scripts/migrate.mjs +265 -0
  87. package/scripts/no-foreign-vocabulary.mjs +216 -0
  88. package/scripts/no-product-names.mjs +157 -0
  89. package/scripts/no-tenant-globals.mjs +1134 -0
  90. package/scripts/offline.mjs +247 -0
  91. package/scripts/og.mjs +170 -0
  92. package/scripts/one-tenant-resolver.mjs +146 -0
  93. package/scripts/open.mjs +63 -0
  94. package/scripts/publish.mjs +1041 -0
  95. package/scripts/read.mjs +31 -0
  96. package/scripts/refine.mjs +455 -0
  97. package/scripts/release-drift.mjs +138 -0
  98. package/scripts/restore.mjs +322 -0
  99. package/scripts/review.mjs +125 -0
  100. package/scripts/save.mjs +21 -0
  101. package/scripts/shell-lint.mjs +189 -0
  102. package/scripts/ship.mjs +460 -0
  103. package/scripts/shoot.mjs +264 -0
  104. package/scripts/smoke-invite.mjs +113 -0
  105. package/scripts/state-inventory.mjs +223 -0
  106. package/scripts/status.mjs +177 -0
  107. package/scripts/sync.mjs +27 -0
  108. package/scripts/tenant-do-rehearsal.mjs +1314 -0
  109. package/scripts/ui-copy-lint.mjs +202 -0
  110. package/scripts/watch.mjs +32 -0
  111. package/scripts/wrangler-preflight.mjs +246 -0
  112. package/seed/CANON.md +43 -0
  113. package/seed/README.md +119 -0
  114. package/seed/prototype-status.json +9 -0
  115. package/seed/registry.json +88 -0
  116. package/seed/skills/starter-ui/SKILL.md +52 -0
  117. package/seed/skills/starter-ui/skill.json +11 -0
  118. package/seed/skills/starter-ui/starter-tokens.css +73 -0
  119. package/seed/skills/starter-ui/starter-ui.css +318 -0
  120. package/seed/skills/starter-ui/starter-ui.js +50 -0
  121. package/seed/space.json +5 -0
  122. package/seed/start-here/prototypes/connect-your-terminal/index.html +428 -0
  123. package/seed/start-here/prototypes/connect-your-terminal/preview.webp +0 -0
  124. package/seed/start-here/prototypes/sample-with-comments/index.html +172 -0
  125. package/seed/start-here/prototypes/sample-with-comments/preview.webp +0 -0
  126. package/seed/start-here/prototypes/set-up-your-design-system/index.html +353 -0
  127. package/seed/start-here/prototypes/set-up-your-design-system/preview.webp +0 -0
  128. package/seed/threads.json +52 -0
  129. package/seed/worked-examples/prototypes/field-readings/index.html +273 -0
  130. package/seed/worked-examples/prototypes/field-readings/preview.webp +0 -0
  131. package/seed/worked-examples/prototypes/slide-deck/index.html +214 -0
  132. package/seed/worked-examples/prototypes/slide-deck/preview.webp +0 -0
  133. package/seed/worked-examples/prototypes/specimen-viewer/index.html +203 -0
  134. package/seed/worked-examples/prototypes/specimen-viewer/preview.webp +0 -0
  135. package/src/_worker.js +12668 -0
  136. package/src/board-key.mjs +57 -0
  137. package/src/board-room.mjs +881 -0
  138. package/src/bundle-keys.mjs +138 -0
  139. package/src/canon/collect-in-browser.js +215 -0
  140. package/src/canon/emit.mjs +232 -0
  141. package/src/canon/observe.mjs +260 -0
  142. package/src/canon/schema.mjs +451 -0
  143. package/src/canvas/DSEG-LICENSE.txt +95 -0
  144. package/src/canvas/DSEG7Classic-Bold.woff2 +0 -0
  145. package/src/canvas/canvas.css +885 -0
  146. package/src/canvas/canvas.js +5780 -0
  147. package/src/canvas/capture.js +485 -0
  148. package/src/chrome/appchrome.mjs +568 -0
  149. package/src/currency.mjs +203 -0
  150. package/src/delete-confirmation.mjs +254 -0
  151. package/src/drafts/drafts.js +326 -0
  152. package/src/entry.js +58 -0
  153. package/src/galleries.mjs +400 -0
  154. package/src/health-cron.mjs +173 -0
  155. package/src/identity.json +1 -0
  156. package/src/kv-codec.mjs +135 -0
  157. package/src/kv-identity.mjs +259 -0
  158. package/src/mail.mjs +599 -0
  159. package/src/provenance.mjs +74 -0
  160. package/src/publish-compose.mjs +363 -0
  161. package/src/publish-fork.mjs +221 -0
  162. package/src/publish-units.mjs +44 -0
  163. package/src/purge.mjs +81 -0
  164. package/src/review/COMMENTING-UX.md +306 -0
  165. package/src/review/cat.png +0 -0
  166. package/src/review/comment-cursor.svg +7 -0
  167. package/src/review/comments.js +1738 -0
  168. package/src/room-ticket.mjs +96 -0
  169. package/src/roster-chip.mjs +32 -0
  170. package/src/seed-pack.mjs +261 -0
  171. package/src/state-inventory.mjs +249 -0
  172. package/src/sw-logic.mjs +48 -0
  173. package/src/tenant-cache.mjs +141 -0
  174. package/src/tenant-context.mjs +386 -0
  175. package/src/tenant-do.js +2940 -0
  176. package/src/tenant-host.mjs +144 -0
  177. package/src/tenant-quotas.mjs +140 -0
  178. package/src/unit-core.mjs +103 -0
  179. package/src/unit-object.mjs +338 -0
  180. package/templates/README.md +67 -0
  181. package/templates/shell/deploy.yml +145 -0
  182. package/templates/shell/engine-bump.yml +142 -0
  183. package/templates/shell/health.yml +233 -0
  184. package/templates/shell/kv-backup.yml +201 -0
  185. package/templates/shell/roster-update.yml +110 -0
  186. package/templates/shell/space-preflight.yml +35 -0
  187. package/templates/shell/space-rebake.yml +79 -0
  188. package/templates/shell/store-backup.yml +152 -0
  189. package/templates/shell/wrangler.example.toml +266 -0
  190. package/templates/space/publish.yml +91 -0
@@ -0,0 +1,74 @@
1
+ # What is current here — and what has been left behind
2
+
3
+ Making a second version of a screen costs one folder, so a workspace fills up
4
+ with abandoned paths faster than anyone tidies them. Nothing breaks when it does:
5
+ the dead prototype still opens, still looks finished, and still sits in the
6
+ gallery next to the live one. That is the failure to watch for — a shelf where
7
+ current and abandoned look identical has stopped being a repository, and it never
8
+ announces itself.
9
+
10
+ Two facts answer it, and the workspace already records both: the **status**
11
+ somebody set on a unit, and the **date its bytes last changed**. One call returns
12
+ them together for every unit.
13
+
14
+ ## The call
15
+
16
+ ```
17
+ GET https://<your-site>/__publish/<workspace>/currency
18
+ Authorization: Bearer $AUGUR_TOKEN
19
+ ```
20
+
21
+ Same token `augur connect` (or `augur login`) already saved for publishing; scoped to your workspace,
22
+ so it answers about that one and no other. (A signed-in browser reads the same
23
+ answer from `/__currency` — one function, two doors, because a person has a
24
+ session and you have a token.)
25
+
26
+ ```json
27
+ {
28
+ "staleAfterDays": 90,
29
+ "now": "2026-08-27T09:12:04.000Z",
30
+ "since": null,
31
+ "count": 2,
32
+ "units": [
33
+ { "key": "checkout/flow", "path": "/checkout/flow/", "space": "acme",
34
+ "status": "dev-ready", "statusLabel": "Dev ready",
35
+ "editedAt": "2026-08-25T14:02:11.000Z", "by": "a1b2c3d4",
36
+ "ageDays": 2, "stale": false, "when": "Edited 2 days ago" },
37
+ { "key": "checkout/old-flow", "path": "/checkout/old-flow/", "space": "acme",
38
+ "status": null, "statusLabel": null,
39
+ "editedAt": "2025-11-02T08:44:00.000Z", "by": "a1b2c3d4",
40
+ "ageDays": 298, "stale": true, "when": "Untouched for 9 months" }
41
+ ]
42
+ }
43
+ ```
44
+
45
+ `key` is the same key the status chip writes under, so a row joins straight onto
46
+ `/__status`. `by` is a one-way id, never an address — resolve it to a person
47
+ through the roster, not by guessing.
48
+
49
+ ## What changed here lately
50
+
51
+ ```
52
+ GET …/currency?since=14d # 2w, 36h and a bare number of days also work
53
+ ```
54
+
55
+ Narrows `units` to what actually moved in that window — the whole of "what
56
+ happened here recently", in one request, without walking a manifest. A window
57
+ the server cannot read is refused with a `400`, never widened: an answer to a
58
+ different question would read as a busy workspace.
59
+
60
+ ## Three things to know before you use it
61
+
62
+ **`stale` has three values, and `null` is one of them.** A unit published before
63
+ per-file dates existed carries no date, so the answer is "no record" rather than
64
+ either extreme. Do not treat `null` as fresh, and do not treat it as abandoned —
65
+ say you do not know. It resolves itself the next time that unit is published.
66
+
67
+ **`staleAfterDays` is in the response because it is the instance's number, not
68
+ yours.** Read it; never hardcode 90. A workspace that moves the threshold would
69
+ otherwise keep hearing its old one from you.
70
+
71
+ **Never add a field to record staleness.** It is computed from the date a publish
72
+ already stamps, which is why it needs no upkeep. The tempting fix — a flag on a
73
+ prototype saying it is archived — is accurate only for units somebody came back
74
+ to mark, and the ones that matter are exactly the ones nobody came back to.
@@ -0,0 +1,92 @@
1
+ # Drafts — how a prototype is changed on a workspace that serves them
2
+
3
+ **Does this apply here?** Ask the instance: `GET /.well-known/augur.json` carries
4
+ `drafts.enabled`. `true` → this document. `false` → [publishing.md](./publishing.md), and
5
+ `augur ship` as before. `augur ship` on a drafts workspace refuses and points here.
6
+
7
+ ## The whole day, in four lines
8
+
9
+ ```
10
+ augur open <opportunity>/<prototype> # a folder of its own, live at once at its draft address
11
+ …edit the folder… # every save is live at that address before your next step
12
+ augur land [-m "what changed"] # the real URL moves; the LAST LINE of stdout is the live URL
13
+ augur sync # only when land was refused: fold main into your draft, land again
14
+ ```
15
+
16
+ That is it. There is no commit, no push, no build. The URL `land` prints is what you hand
17
+ the person you are working for — never a localhost, never a `file://` path.
18
+
19
+ A prototype that does not exist yet: `augur open --new <opportunity>/<name>` gives you an
20
+ empty folder and an empty draft; write its `index.html` and land. The same works for a
21
+ library demo (`components/<name>`, `base/…`, `patterns/…`, `pages/…`) and for the
22
+ workspace's design system (`skills/<prefix>-ui`), which is one unit like any other.
23
+ Without `--new`, a name that does not exist is refused rather than guessed at.
24
+
25
+ ## What a draft is
26
+
27
+ Your own live copy of one prototype, at its own address: the prototype's URL with `@` and
28
+ a short id, for example `/checkout/flow/@k7f3q1/`. Everyone signed in to the workspace
29
+ can look at it while you work; the prototype's real URL keeps serving what it served until
30
+ you land. Two agents opening the same prototype get two drafts, are both told about each
31
+ other, and both work — nothing locks, nobody waits.
32
+
33
+ `augur open` prints who else has it open. That is the whole coordination step; there is
34
+ nothing to leave and nothing to clear (working marks are for workspaces without drafts).
35
+
36
+ ## Editing
37
+
38
+ Edit the files in the folder `open` created. That folder holds that prototype and
39
+ nothing else. Your editor's hooks — installed the first time `augur open` ran on this
40
+ machine — save every edit to the draft before your next tool call, so the draft address
41
+ is always what your files are. A refused save comes back in your next tool result with
42
+ the reason; read it, it says what to do.
43
+
44
+ The hooks also refuse two kinds of write, with the reason:
45
+
46
+ - a file under a prototype in a **shared checkout** (a clone of the workspace) — open the
47
+ prototype instead; that is what the refusal tells you to run;
48
+ - a file in a **read-only copy** (`augur read <unit>` puts one under `_read/`, for
49
+ context) — open the prototype if you mean to change it.
50
+
51
+ Everything else — the workspace's design system, its docs, other projects — is untouched.
52
+ If you are editing by hand in an editor with no hooks, `augur watch` inside the folder
53
+ saves on every burst of changes; `augur save` saves once.
54
+
55
+ ## Landing
56
+
57
+ `augur land` replaces the prototype's real URL with your draft, records who landed it and
58
+ when, and closes the draft. It is refused in exactly one case: somebody landed on this
59
+ prototype since you opened yours. Then:
60
+
61
+ ```
62
+ augur sync # takes main's changes into your folder; a real overlap is left for you to fold
63
+ augur land # again
64
+ ```
65
+
66
+ `sync` writes one-sided changes outright and merges a file you both touched when the
67
+ edits do not overlap. When they do overlap, your version stays in place, theirs is put at
68
+ `.augur/theirs/<path>`, and the overlapping hunks are printed — fold them, then land.
69
+ Nothing is guessed, on the server or here.
70
+
71
+ `augur close` removes the folder once landed. `augur close --discard` abandons a draft
72
+ you do not want (its saves stay on the instance for a while; nothing else is touched).
73
+ `augur status` lists the drafts open on this machine and who else is on those prototypes.
74
+
75
+ ## When something is refused
76
+
77
+ | It says | What happened | Do |
78
+ |---|---|---|
79
+ | `main-moved` / "sync first" | somebody landed since you opened | `augur sync`, then `augur land` |
80
+ | `stale-draft` | another process saved to this same draft | `augur sync`, then `augur save` |
81
+ | `would-unpublish` | the draft has no files (the folder is empty) | check the folder; a deletion is its own verb |
82
+ | `not-a-prototype-folder` / `reserved-folder` | the path is not `<opportunity>/<prototype>` | name the prototype folder |
83
+ | `units-not-configured` | this instance does not serve drafts | use `augur ship` — see publishing.md |
84
+ | no publish token | this machine is not paired | `augur connect` (never a password) |
85
+ | unreachable | the instance could not be reached | nothing is lost; the next save carries every change since |
86
+
87
+ ## What you never do
88
+
89
+ - Ship a whole tree to a drafts workspace. `ship` refuses; `--legacy` exists for one
90
+ release and is not for you.
91
+ - Wait for, or refuse over, somebody else's draft. Both work; the second landing syncs.
92
+ - Hand over a path on disk as "done". Done is the URL `land` printed.
@@ -0,0 +1,148 @@
1
+ # Users, login, avatars
2
+
3
+ ## Getting in, if you are an agent
4
+
5
+ You do not need a login. Publishing needs a **publish token**, and the way to get one
6
+ is device pairing: `npx augur connect --origin <the workspace>` prints a link and a
7
+ code, the owner enters the code in a browser they are already signed in to, and the
8
+ token lands on this machine. Nobody's password is asked for, typed or stored. The
9
+ instance says the same at `GET /llms.txt`. The full shape is in [README.md](./README.md)
10
+ under *Getting in*.
11
+
12
+ The `pass` field on a user record below is a first-sign-in seed for a **self-hosted**
13
+ instance, consulted only when that person has no credential in the store yet. It is not
14
+ a key: on a hosted workspace it is dead, on a self-hosted one it stops working the moment
15
+ the person sets a password, and the gate throttles failed attempts. Never try it.
16
+
17
+ ## The account model
18
+
19
+ The engine has per-user accounts. Sign-in is email + password on a self-hosted
20
+ instance, or an emailed code from the central account store on a hosted one (no
21
+ password exists there; see `ACCOUNT_ORIGIN` / `SESSION_KEYS`). The engine repo
22
+ itself carries **no users** — `src/identity.json` is an empty placeholder; the
23
+ live list lives in the instance's deploy shell repo as `identity.json` (NOT in
24
+ this engine repo). Editing it is a config change: it only reaches the live site
25
+ once the instance config is redeployed/published (build re-emits
26
+ `dist/__config/instance.json` from it), never on a bare file save.
27
+
28
+ ## The user record
29
+
30
+ `identity.json` is an ARRAY of user objects:
31
+
32
+ ```jsonc
33
+ {
34
+ "email": "person@example.org", // login id
35
+ "emails": ["alias@example.org"], // optional extra addresses that also log in
36
+ "name": "Person Name",
37
+ "role": "admin", // "admin" | "editor" | "viewer". Omit for editor.
38
+ // (`user` is the legacy spelling of "editor" and
39
+ // still reads as one — never a flag day — but
40
+ // nothing new should be written wearing it.)
41
+ "passHash": "pbkdf2$…", // FIRST ADMIN ONLY — everyone else is invited
42
+ "initials": "PN", // presence chip fallback
43
+ "color": "#7A5AF8", // presence chip color
44
+ "avatar": "data:image/webp;base64,…" // optional SEED photo; served at /__avatar/<key>
45
+ }
46
+ ```
47
+
48
+ ### The three roles
49
+
50
+ | | Sign in, comment, drive boards | Publish | Admin panel, tokens, delete |
51
+ |---|---|---|---|
52
+ | `viewer` | ✅ | ❌ | ❌ |
53
+ | `editor` | ✅ | ✅ | ❌ |
54
+ | `admin` | ✅ | ✅ | ✅ |
55
+
56
+ `viewer` is the role for an account whose password is public knowledge — a demo
57
+ instance's `loginHint` credentials. It is refused a publish token at mint time, and
58
+ any token it already holds stops resolving, so a demotion cannot leave the old
59
+ privilege alive in a credential.
60
+
61
+ **Changing a role** is a per-person control in the Admin panel (click a row →
62
+ Role). It takes effect on the next request via a KV overlay, and the panel asks the
63
+ deploy shell to commit the change to `identity.json` so the file stays the durable
64
+ record — at which point the overlay entry drains itself. The one refusal: the **last
65
+ admin cannot be demoted**, because an instance with no admin cannot be repaired from
66
+ inside it (every admin route, the panel and the star-scoped publish token all
67
+ require one).
68
+ Promote someone else first.
69
+
70
+ - **The panel can add and remove people without a commit, and the two records
71
+ converge.** The Admin page lists everyone as a table (name + email, role, last
72
+ active) with an **Invite** action and, on clicking a row, **Reset password** /
73
+ **Remove user**. Invite and remove write a runtime overlay on top of the file, so
74
+ the change is live instantly; the same action asks the deploy shell to commit it
75
+ to `identity.json`, and when the deploy that follows pushes the new config back,
76
+ the worker DRAINS every overlay entry the file now supersedes.
77
+
78
+ ⚠️ **The overlay is transitional by design, not a second record.** Left
79
+ un-drained the two disagree visibly: builds bake people-derived state into every
80
+ generated page, so an identity-file build and a live-roster build disagree about
81
+ who exists, and each publish flips hundreds of gallery pages between the two
82
+ renderings. `identity.json` stays the durable record — edit it when a change
83
+ should outlive the instance — while day-to-day onboarding is a click.
84
+ - **Credentials are invite-set, never issued.** `identity.json` is the ROSTER —
85
+ who exists, not what they know. A new user is added with NO password; the admin
86
+ panel's **Reset / invite** action mints a single-use link (`/__invite?t=…`,
87
+ 7-day expiry) that the maintainer copies and sends manually. Opening it lets the
88
+ user choose their own password. Passwords live only as PBKDF2 hashes in KV
89
+ (`users:secrets`) — the operator never sets, reads, or can recover one; "reset"
90
+ just revokes the old hash and mints a fresh invite. The one account that cannot
91
+ be invited is the **first admin** of a new instance; seed that one with a
92
+ `passHash` (`hashPassword` in the worker generates it). Password verification
93
+ accepts `pbkdf2$…` strings and nothing else, so a **plaintext** value in the
94
+ file is worse than useless: it resolves as that user's secret, which makes the
95
+ account read as active rather than pending, while no password on earth verifies
96
+ against it.
97
+ Account states: **pending** (on the roster, no hash → can't log in yet),
98
+ **accepted** (has set a password), **active** (accepted + recently seen).
99
+ Resetting or changing a password signs that user out (the session changes, so
100
+ their cookie stops matching) and revokes every publish token they minted.
101
+
102
+ ⚠️ **A reset writes a TOMBSTONE, not a deletion**, and the difference is the
103
+ whole of the guarantee: a key that is present holding `null` reads as "no
104
+ secret", where an ABSENT key falls back to whatever `passHash` the roster seeded.
105
+ Tidying a tombstone away would put a reset password straight back in service.
106
+ - **Photos are self-serve, and the file does NOT win.** Anyone signed in sets
107
+ their own from the profile menu (Add / Change / Remove photo): the browser
108
+ square-crops and downscales the file to ~192px, then `POST /__me/avatar` stores
109
+ it in KV — the image under `avatar:<hash>`, a one-line pointer per person in
110
+ `users:avatars` — and it serves ungated at `/__avatar/u/<hash>` so presence
111
+ chips and contributor faces work on public pages. An `avatar` data-URI in
112
+ `identity.json` is a **seed**: it shows until that person sets their own, and
113
+ it comes back if they remove it. This is the one field where the runtime
114
+ overlay beats the config file, deliberately — a person's face is theirs.
115
+ A photo kept in `identity.json` keeps working as a seed, and can be dropped
116
+ once that person has set their own.
117
+ Admins cannot set someone else's photo (there is no email parameter on the
118
+ route) — removing a user clears theirs.
119
+ - Publishing does not need the identity file: `publish.mjs` fetches sanitized
120
+ contributor profiles from the live worker (`/__publish/_instance/profiles`)
121
+ when no identity file is around, so bare-clone publishes keep the faces.
122
+
123
+ ## ⏳ Where this is going, and what is already true
124
+
125
+ A workspace is becoming the only tier, and identity is splitting in two — a
126
+ **credential is account-level** (one address, one password, several workspaces)
127
+ and **membership is workspace-level**. That is not cosmetic: an admin who could
128
+ reset a shared credential would silently become an admin of their colleague's
129
+ unrelated workspace, so a workspace's own store holds no password and no hash of
130
+ one, by construction rather than by care.
131
+
132
+ What is already true, and what a doc reader should not be surprised by:
133
+
134
+ - **A publish token expires.** Thirty days by default, set per instance. It used
135
+ to last forever. Where the instance offers device pairing, a missing or expired
136
+ token pairs itself inside the publish (`augur connect`: a link and a code for a
137
+ signed-in browser); see [publishing.md](./publishing.md) for the refusals and
138
+ what each one does.
139
+ - **A viewer cannot hold one at all**, and a demotion invalidates the tokens the
140
+ account already minted rather than leaving old privilege alive in a credential.
141
+ - **Removal is not erasure.** Removing somebody revokes their access and leaves
142
+ their name on the comments they wrote, deliberately: the thread is a record
143
+ other people are part of. Erasure is a separate, explicit act that redacts
144
+ authorship and keeps every message body and every reply intact.
145
+ - **The self-hosted single-instance shape above is not going away.** `identity.json`
146
+ plus an admin-issued invite is how an instance with no account service works, and
147
+ it stays the recovery path for one — that is the case a hosted platform's
148
+ account-level credential does not cover.
@@ -0,0 +1,69 @@
1
+ # What the engine expects of a prototype
2
+
3
+ A prototype is a folder of **self-contained static HTML/JS** under
4
+ `<opportunity>/prototypes/<name>/` with `index.html` as the entry. No build
5
+ step: it must work opened directly (`file://`). The contents of `prototypes/`
6
+ folders are published — `research.md`, `context.md`, anything else outside a
7
+ published folder stays internal by the build's whitelist. **`playground/` also
8
+ ships** (verbatim, to the public `/playground/`), so nothing private belongs
9
+ there either.
10
+
11
+ That `file://`-ability is a property of the *files* — it keeps them portable and
12
+ buildless, nothing more. It is not a hand-off channel: publish the change and
13
+ share the live URL (see `agents/publishing.md`). The only acceptable local
14
+ stand-in, and only when publishing is genuinely impossible, is the real shell
15
+ (`node ../augur/scripts/dev.mjs`), said out loud to be exactly that — local
16
+ only, not shipped, nobody else can see it.
17
+
18
+ Neither is ever offered as a choice. An agent doesn't ask "should I publish
19
+ this?" or present "local or deployed?" as two valid outcomes — the moment a
20
+ prototype works, it ships, and the reply is the live URL that follows from
21
+ that, not a question that precedes it.
22
+
23
+ Beyond that, the engine reads a few things *from* your prototype. None are
24
+ required, but each one improves how the site presents and reviews it:
25
+
26
+ ## `<meta name="description">` — the prototype's one-line description
27
+
28
+ The engine treats it as the prototype's agent-facing and human-facing blurb:
29
+ gallery cards, link previews (OG description), and the canvas insert-picker
30
+ catalog all carry it. Write one sentence stating what the prototype shows.
31
+ It lives in the artifact, so it updates in the same commit that changes the
32
+ prototype — keep it true.
33
+
34
+ ## `<body data-gv-screen="…">` — the screen contract for SPAs
35
+
36
+ The comment overlay scopes each pin to the screen it was dropped on.
37
+ Multi-page prototypes need nothing (the URL distinguishes screens). A
38
+ prototype that **swaps screens without changing the URL** must publish its
39
+ visible state to `<body data-gv-screen>` (update it whenever the screen
40
+ changes) — otherwise every screen shows every comment, and pins appear to
41
+ float on the wrong UI.
42
+
43
+ ## Titles and display names
44
+
45
+ The folder name is the URL segment (kebab-case, name it like a URL). The
46
+ site's display name for a prototype or opportunity can be renamed live from
47
+ the site chrome (stored server-side, in KV — not in your repo), so don't fight
48
+ a bad folder name after the fact; rename the display instead.
49
+
50
+ ## Status chips
51
+
52
+ An optional dev-status chip per prototype comes from `prototype-status.json`
53
+ at the workspace root (see its `_comment` for the format; values are `in-progress`,
54
+ `dev-ready`, `ignore`); statuses are also cycled by clicking the
55
+ chip on the live site (stored in KV, overlaying the committed baseline).
56
+
57
+ ## Posters and link cards
58
+
59
+ A `preview.webp` (gallery card) and `og.jpg` (link share) in the prototype
60
+ folder are welcome but not required — without them the card falls back to a live
61
+ iframe and links get no image; maintainers shoot them with `scripts/shoot.mjs` /
62
+ `scripts/og.mjs`.
63
+
64
+ ## Comments overlay assets
65
+
66
+ Any file the overlay must load from a public page has to be public too — if an
67
+ image inside your prototype 404s or renders as a login page when logged out,
68
+ check it lives inside the prototype folder (published) rather than outside
69
+ (internal).