@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.
- package/CANVAS.md +858 -0
- package/INSTALL.md +603 -0
- package/LICENSE +27 -0
- package/README.md +150 -0
- package/TRADEMARKS.md +72 -0
- package/agents/README.md +65 -0
- package/agents/canon-extract.md +117 -0
- package/agents/canon.md +151 -0
- package/agents/canvas.md +88 -0
- package/agents/currency.md +74 -0
- package/agents/drafts.md +92 -0
- package/agents/identity.md +148 -0
- package/agents/prototype-contract.md +69 -0
- package/agents/publishing.md +324 -0
- package/agents/review-feedback.md +76 -0
- package/agents/space-json.md +132 -0
- package/agents/ui-skill.md +101 -0
- package/agents/working-marks.md +86 -0
- package/brand/augur-eye.svg +3 -0
- package/brand/augur-icon.svg +41 -0
- package/brand/augur-mark.svg +5 -0
- package/brand/lentianova-bulky.otf +0 -0
- package/brand/lentianova-median.otf +0 -0
- package/brand/lentianova.woff2 +0 -0
- package/build.js +8409 -0
- package/changelog.md +133 -0
- package/fonts/inter-latin-wght-normal.woff2 +0 -0
- package/package.json +57 -0
- package/pitis/PETS.md +121 -0
- package/pitis/README.md +117 -0
- package/pitis/index.html +266 -0
- package/pitis/piti.build.js +81 -0
- package/pitis/piti.js +929 -0
- package/pitis/roast-agent.md +185 -0
- package/realtime/src/index.js +119 -0
- package/realtime/wrangler.example.toml +44 -0
- package/scripts/adopt.mjs +106 -0
- package/scripts/board-snapshot.mjs +197 -0
- package/scripts/build-seed-pack.mjs +33 -0
- package/scripts/bundle-rekey.mjs +112 -0
- package/scripts/bundle-tenancy-rehearsal.mjs +781 -0
- package/scripts/canon-extract.mjs +578 -0
- package/scripts/canon.mjs +599 -0
- package/scripts/canvas-screen.mjs +333 -0
- package/scripts/check-local.mjs +136 -0
- package/scripts/clawd-art.mjs +75 -0
- package/scripts/clawd-canvas.mjs +741 -0
- package/scripts/cli.mjs +100 -0
- package/scripts/clone.mjs +422 -0
- package/scripts/close.mjs +30 -0
- package/scripts/connect.mjs +103 -0
- package/scripts/deploy.mjs +157 -0
- package/scripts/dev.mjs +54 -0
- package/scripts/doc-lint.mjs +143 -0
- package/scripts/export.mjs +300 -0
- package/scripts/fork.mjs +64 -0
- package/scripts/freeze.mjs +74 -0
- package/scripts/frontdoor-parity.mjs +180 -0
- package/scripts/hook.mjs +67 -0
- package/scripts/identity-rekey.mjs +112 -0
- package/scripts/init.mjs +122 -0
- package/scripts/land.mjs +40 -0
- package/scripts/lib/adapters.mjs +156 -0
- package/scripts/lib/board-snapshot.mjs +511 -0
- package/scripts/lib/draft.mjs +385 -0
- package/scripts/lib/graduate.mjs +276 -0
- package/scripts/lib/instance.mjs +74 -0
- package/scripts/lib/marks.mjs +107 -0
- package/scripts/lib/materialize.mjs +138 -0
- package/scripts/lib/merge3.mjs +124 -0
- package/scripts/lib/offline-posture.mjs +120 -0
- package/scripts/lib/offline-respawn.mjs +18 -0
- package/scripts/lib/offline-wrangler.mjs +78 -0
- package/scripts/lib/publish-compose.mjs +12 -0
- package/scripts/lib/publish-conflict.mjs +78 -0
- package/scripts/lib/publish-evidence.mjs +144 -0
- package/scripts/lib/refine-compare.mjs +140 -0
- package/scripts/lib/refine-ledger.mjs +150 -0
- package/scripts/lib/refine-png.mjs +149 -0
- package/scripts/lib/seed-pack-build.mjs +141 -0
- package/scripts/lib/state-compare.mjs +68 -0
- package/scripts/lib/store.mjs +122 -0
- package/scripts/login.mjs +84 -0
- package/scripts/mark.mjs +112 -0
- package/scripts/migrate-board-keys.mjs +164 -0
- package/scripts/migrate.mjs +265 -0
- package/scripts/no-foreign-vocabulary.mjs +216 -0
- package/scripts/no-product-names.mjs +157 -0
- package/scripts/no-tenant-globals.mjs +1134 -0
- package/scripts/offline.mjs +247 -0
- package/scripts/og.mjs +170 -0
- package/scripts/one-tenant-resolver.mjs +146 -0
- package/scripts/open.mjs +63 -0
- package/scripts/publish.mjs +1041 -0
- package/scripts/read.mjs +31 -0
- package/scripts/refine.mjs +455 -0
- package/scripts/release-drift.mjs +138 -0
- package/scripts/restore.mjs +322 -0
- package/scripts/review.mjs +125 -0
- package/scripts/save.mjs +21 -0
- package/scripts/shell-lint.mjs +189 -0
- package/scripts/ship.mjs +460 -0
- package/scripts/shoot.mjs +264 -0
- package/scripts/smoke-invite.mjs +113 -0
- package/scripts/state-inventory.mjs +223 -0
- package/scripts/status.mjs +177 -0
- package/scripts/sync.mjs +27 -0
- package/scripts/tenant-do-rehearsal.mjs +1314 -0
- package/scripts/ui-copy-lint.mjs +202 -0
- package/scripts/watch.mjs +32 -0
- package/scripts/wrangler-preflight.mjs +246 -0
- package/seed/CANON.md +43 -0
- package/seed/README.md +119 -0
- package/seed/prototype-status.json +9 -0
- package/seed/registry.json +88 -0
- package/seed/skills/starter-ui/SKILL.md +52 -0
- package/seed/skills/starter-ui/skill.json +11 -0
- package/seed/skills/starter-ui/starter-tokens.css +73 -0
- package/seed/skills/starter-ui/starter-ui.css +318 -0
- package/seed/skills/starter-ui/starter-ui.js +50 -0
- package/seed/space.json +5 -0
- package/seed/start-here/prototypes/connect-your-terminal/index.html +428 -0
- package/seed/start-here/prototypes/connect-your-terminal/preview.webp +0 -0
- package/seed/start-here/prototypes/sample-with-comments/index.html +172 -0
- package/seed/start-here/prototypes/sample-with-comments/preview.webp +0 -0
- package/seed/start-here/prototypes/set-up-your-design-system/index.html +353 -0
- package/seed/start-here/prototypes/set-up-your-design-system/preview.webp +0 -0
- package/seed/threads.json +52 -0
- package/seed/worked-examples/prototypes/field-readings/index.html +273 -0
- package/seed/worked-examples/prototypes/field-readings/preview.webp +0 -0
- package/seed/worked-examples/prototypes/slide-deck/index.html +214 -0
- package/seed/worked-examples/prototypes/slide-deck/preview.webp +0 -0
- package/seed/worked-examples/prototypes/specimen-viewer/index.html +203 -0
- package/seed/worked-examples/prototypes/specimen-viewer/preview.webp +0 -0
- package/src/_worker.js +12668 -0
- package/src/board-key.mjs +57 -0
- package/src/board-room.mjs +881 -0
- package/src/bundle-keys.mjs +138 -0
- package/src/canon/collect-in-browser.js +215 -0
- package/src/canon/emit.mjs +232 -0
- package/src/canon/observe.mjs +260 -0
- package/src/canon/schema.mjs +451 -0
- package/src/canvas/DSEG-LICENSE.txt +95 -0
- package/src/canvas/DSEG7Classic-Bold.woff2 +0 -0
- package/src/canvas/canvas.css +885 -0
- package/src/canvas/canvas.js +5780 -0
- package/src/canvas/capture.js +485 -0
- package/src/chrome/appchrome.mjs +568 -0
- package/src/currency.mjs +203 -0
- package/src/delete-confirmation.mjs +254 -0
- package/src/drafts/drafts.js +326 -0
- package/src/entry.js +58 -0
- package/src/galleries.mjs +400 -0
- package/src/health-cron.mjs +173 -0
- package/src/identity.json +1 -0
- package/src/kv-codec.mjs +135 -0
- package/src/kv-identity.mjs +259 -0
- package/src/mail.mjs +599 -0
- package/src/provenance.mjs +74 -0
- package/src/publish-compose.mjs +363 -0
- package/src/publish-fork.mjs +221 -0
- package/src/publish-units.mjs +44 -0
- package/src/purge.mjs +81 -0
- package/src/review/COMMENTING-UX.md +306 -0
- package/src/review/cat.png +0 -0
- package/src/review/comment-cursor.svg +7 -0
- package/src/review/comments.js +1738 -0
- package/src/room-ticket.mjs +96 -0
- package/src/roster-chip.mjs +32 -0
- package/src/seed-pack.mjs +261 -0
- package/src/state-inventory.mjs +249 -0
- package/src/sw-logic.mjs +48 -0
- package/src/tenant-cache.mjs +141 -0
- package/src/tenant-context.mjs +386 -0
- package/src/tenant-do.js +2940 -0
- package/src/tenant-host.mjs +144 -0
- package/src/tenant-quotas.mjs +140 -0
- package/src/unit-core.mjs +103 -0
- package/src/unit-object.mjs +338 -0
- package/templates/README.md +67 -0
- package/templates/shell/deploy.yml +145 -0
- package/templates/shell/engine-bump.yml +142 -0
- package/templates/shell/health.yml +233 -0
- package/templates/shell/kv-backup.yml +201 -0
- package/templates/shell/roster-update.yml +110 -0
- package/templates/shell/space-preflight.yml +35 -0
- package/templates/shell/space-rebake.yml +79 -0
- package/templates/shell/store-backup.yml +152 -0
- package/templates/shell/wrangler.example.toml +266 -0
- 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.
|
package/agents/drafts.md
ADDED
|
@@ -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).
|