@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
package/INSTALL.md
ADDED
|
@@ -0,0 +1,603 @@
|
|
|
1
|
+
# INSTALL — stand up an Augur instance
|
|
2
|
+
|
|
3
|
+
The complete recipe, start to finish. It is written to be **executed top to bottom by an
|
|
4
|
+
agent**: every step is a command with a check, and the handful of things only a person can
|
|
5
|
+
do are marked **HUMAN** — stop there, ask, continue.
|
|
6
|
+
|
|
7
|
+
Budget: about an hour, most of it waiting on DNS and CI.
|
|
8
|
+
|
|
9
|
+
If you only want to run the engine locally against some spaces, you do not need any of
|
|
10
|
+
this — see [README.md](./README.md).
|
|
11
|
+
|
|
12
|
+
## What you are building
|
|
13
|
+
|
|
14
|
+
An instance is **three repos and one Cloudflare project**:
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
<shell repo> private the deploy shell: engine pin, identity.json, deploy.config.json,
|
|
18
|
+
CI workflows, every secret. Ships the site's CODE.
|
|
19
|
+
<space repo> private the content: space.json + <project>/prototypes/**. One repo per
|
|
20
|
+
space; a design system is optional.
|
|
21
|
+
augur public this engine. Pinned as a submodule. Deploys nothing itself.
|
|
22
|
+
|
|
23
|
+
Cloudflare Pages project + KV namespace + R2 bucket (+ optional realtime worker)
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Two things move independently, and keeping them straight is the whole model:
|
|
27
|
+
|
|
28
|
+
| What | Ships by | Takes |
|
|
29
|
+
|---|---|---|
|
|
30
|
+
| Engine code + shared chrome | push to the shell (or an engine pin bump) | ~1 min, via CI |
|
|
31
|
+
| Space content | `augur publish` from a space clone | seconds, no CI |
|
|
32
|
+
|
|
33
|
+
**Rule zero: pin, don't fork.** Never patch the engine inside a shell or a space, never
|
|
34
|
+
run an instance off a private engine fork. A patched instance stops taking upstream
|
|
35
|
+
fixes. Engine gaps get fixed upstream (generic, no instance-specific words) and every
|
|
36
|
+
instance takes them by pin bump. See [CONTRIBUTING.md](./CONTRIBUTING.md).
|
|
37
|
+
|
|
38
|
+
**R2 is required.** CI builds the shell with `GV_ENGINE_ONLY=1` — no space is ever on
|
|
39
|
+
disk there — so the bundle store is the only path content has to the live site. An
|
|
40
|
+
instance without R2 serves chrome and nothing else.
|
|
41
|
+
|
|
42
|
+
## Step 0 — fill this in
|
|
43
|
+
|
|
44
|
+
Decide these first and keep them at hand; every later step substitutes from this table.
|
|
45
|
+
|
|
46
|
+
| Value | Placeholder | Example |
|
|
47
|
+
|---|---|---|
|
|
48
|
+
| Instance slug | `<instance>` | `acme` |
|
|
49
|
+
| Shell repo | `<owner>/<shell>` | `acme-co/augur-deploy-acme` |
|
|
50
|
+
| Space repo | `<owner>/<space-repo>` | `acme-co/augur-space-acme` |
|
|
51
|
+
| Space id (from its `space.json`) | `<space-id>` | `acme` |
|
|
52
|
+
| Pages project | `<pages-project>` | `augur-acme` |
|
|
53
|
+
| KV namespace | `<kv-id>` | filled in at step 4 |
|
|
54
|
+
| R2 bucket | `<bucket>` | `augur-acme-bundles` |
|
|
55
|
+
| Site origin | `<site>` | `https://augur-acme.pages.dev` |
|
|
56
|
+
| Site host (origin without the scheme) | `<site-host>` | `augur-acme.pages.dev` |
|
|
57
|
+
| Admin email | `<admin-email>` | the first user, `role: "admin"` |
|
|
58
|
+
|
|
59
|
+
The space id is the mount name and the URL prefix; the repo name is a free label. The
|
|
60
|
+
**default** space owns the site root, every other space serves under `/<space-id>/`.
|
|
61
|
+
|
|
62
|
+
## Step 1 — HUMAN: credentials
|
|
63
|
+
|
|
64
|
+
Three things an agent cannot obtain. Ask for them all at once.
|
|
65
|
+
|
|
66
|
+
1. **Cloudflare API token** (dashboard → My Profile → API Tokens → Create). Account-scoped
|
|
67
|
+
permissions: `Cloudflare Pages: Edit`, `Workers KV Storage: Edit`,
|
|
68
|
+
`Workers R2 Storage: Edit`, and `Workers Scripts: Edit` if you want canvas multiplayer.
|
|
69
|
+
Add `Zone → DNS: Edit` on the zone if you want a custom domain automated.
|
|
70
|
+
2. **Cloudflare account id** (dashboard sidebar, or the URL after `/accounts/`).
|
|
71
|
+
3. **R2 enabled on the account** — a one-time dashboard click (R2 → Enable). The API
|
|
72
|
+
cannot do it and returns error code `10042` until it is done.
|
|
73
|
+
4. **A GitHub PAT**, fine-grained, `Contents: Read and write` on the shell repo, and
|
|
74
|
+
`Contents: Read` on every space repo. One token covers every use below.
|
|
75
|
+
|
|
76
|
+
Export them for the rest of this run:
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
export CF_TOKEN=… CF_ACCOUNT=… GH_TOKEN=…
|
|
80
|
+
export API="https://api.cloudflare.com/client/v4/accounts/$CF_ACCOUNT"
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Check R2 before going further — this is the one gate that bites late:
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
curl -s "$API/r2/buckets" -H "Authorization: Bearer $CF_TOKEN" | grep -q '"code":10042' \
|
|
87
|
+
&& echo "R2 NOT ENABLED — ask the human to click Enable in the dashboard" || echo "R2 ok"
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Step 2 — the local workspace
|
|
91
|
+
|
|
92
|
+
Clone the three repos **side by side** in a parent folder that is not itself a repo. The
|
|
93
|
+
layout is load-bearing: local scripts find the shell by shape (a sibling holding an
|
|
94
|
+
`identity.json`), and offline mode picks up every sibling that has a `space.json`.
|
|
95
|
+
|
|
96
|
+
```
|
|
97
|
+
<parent>/ # not a repo
|
|
98
|
+
├── augur/ # this engine (public)
|
|
99
|
+
├── <shell>/ # the deploy shell
|
|
100
|
+
└── <space-repo>/ # the space — where content is edited
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
mkdir -p <parent> && cd <parent>
|
|
105
|
+
git clone https://github.com/andratwiro/augur.git augur
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
The engine has no runtime dependencies — `login`, `publish`, `build` and `status` run on
|
|
109
|
+
a bare clone with plain `node`. `npm install` is only needed for the Playwright-backed
|
|
110
|
+
screenshot scripts. The commands below call the scripts by path
|
|
111
|
+
(`node <parent>/augur/scripts/<name>.mjs`); `npm link` inside `augur/` gives you the
|
|
112
|
+
shorter `augur <cmd>` form if you want it.
|
|
113
|
+
|
|
114
|
+
Never nest one instance's parent inside another's — offline mode would serve both
|
|
115
|
+
instances' spaces at once.
|
|
116
|
+
|
|
117
|
+
## Step 3 — the space repo
|
|
118
|
+
|
|
119
|
+
Content lives here, and this is the only repo a collaborator ever needs.
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
mkdir -p <space-repo>/<project>/prototypes/hello
|
|
123
|
+
cd <space-repo>
|
|
124
|
+
cat > space.json <<'JSON'
|
|
125
|
+
{
|
|
126
|
+
"id": "<space-id>",
|
|
127
|
+
"name": "<Space name>",
|
|
128
|
+
"default": true,
|
|
129
|
+
"siteOrigin": "<site>"
|
|
130
|
+
}
|
|
131
|
+
JSON
|
|
132
|
+
echo '<!doctype html><meta charset=utf-8><title>Hello</title><h1>Hello</h1>' \
|
|
133
|
+
> <project>/prototypes/hello/index.html
|
|
134
|
+
git init -b main && git add . && git commit -m "space: initial"
|
|
135
|
+
gh repo create <owner>/<space-repo> --private --source=. --push
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
- Only the contents of `prototypes/` folders are published — plus the three small
|
|
139
|
+
documents the build itself reads to compose the space (`registry.json`,
|
|
140
|
+
`prototype-status.json`, the skill's `skill.json`), so a workspace cloned back from
|
|
141
|
+
its publish builds again. Notes, research and anything else outside them stay private
|
|
142
|
+
by construction.
|
|
143
|
+
- A design system is optional. Plain self-contained HTML builds and ships fine. If you
|
|
144
|
+
do add one (`skills/<x>-ui/`), the space also needs a `registry.json` overlay catalog
|
|
145
|
+
at its root — the build refuses to ship an unlabeled overlay. See
|
|
146
|
+
[agents/ui-skill.md](./agents/ui-skill.md).
|
|
147
|
+
- `"default": true` puts the space at the site root. **An instance serves exactly one
|
|
148
|
+
space**, so exactly one space must have it — the old `/<id>/` mount for additional
|
|
149
|
+
spaces is retired and nothing routes to it. Run a second workspace as a second deploy.
|
|
150
|
+
- `"adminOnly"` is **inert** — it still parses, but it seals nothing. It only ever
|
|
151
|
+
applied to a non-default `/<id>/` mount, and those are gone. A workspace is private
|
|
152
|
+
because of who is a member of it, not because of a flag on its path.
|
|
153
|
+
- Every field, with semantics: [agents/space-json.md](./agents/space-json.md).
|
|
154
|
+
|
|
155
|
+
**No CI belongs in a space repo.** Content ships by publishing, not by pushing.
|
|
156
|
+
|
|
157
|
+
## Step 4 — Cloudflare resources
|
|
158
|
+
|
|
159
|
+
```bash
|
|
160
|
+
# KV — overlay state (comments, pins, statuses, sessions, publish tokens)
|
|
161
|
+
curl -s -X POST "$API/storage/kv/namespaces" -H "Authorization: Bearer $CF_TOKEN" \
|
|
162
|
+
-H "content-type: application/json" -d '{"title":"<instance>-kv"}'
|
|
163
|
+
# → note result.id as <kv-id>
|
|
164
|
+
|
|
165
|
+
# R2 — the bundle store (published content)
|
|
166
|
+
curl -s -X POST "$API/r2/buckets" -H "Authorization: Bearer $CF_TOKEN" \
|
|
167
|
+
-H "content-type: application/json" -d '{"name":"<bucket>"}'
|
|
168
|
+
|
|
169
|
+
# Pages project
|
|
170
|
+
curl -s -X POST "$API/pages/projects" -H "Authorization: Bearer $CF_TOKEN" \
|
|
171
|
+
-H "content-type: application/json" -d '{
|
|
172
|
+
"name":"<pages-project>","production_branch":"main",
|
|
173
|
+
"deployment_configs":{
|
|
174
|
+
"production":{"compatibility_date":"2026-06-14",
|
|
175
|
+
"kv_namespaces":{"COMMENTS":{"namespace_id":"<kv-id>"}}},
|
|
176
|
+
"preview": {"compatibility_date":"2026-06-14",
|
|
177
|
+
"kv_namespaces":{"COMMENTS":{"namespace_id":"<kv-id>"}}}}}'
|
|
178
|
+
|
|
179
|
+
# Bindings + runtime secrets on production. PATCH merges, so this keeps the KV binding.
|
|
180
|
+
curl -s -X PATCH "$API/pages/projects/<pages-project>" -H "Authorization: Bearer $CF_TOKEN" \
|
|
181
|
+
-H "content-type: application/json" -d "{\"deployment_configs\":{\"production\":{
|
|
182
|
+
\"r2_buckets\":{\"BUNDLES\":{\"name\":\"<bucket>\"}},
|
|
183
|
+
\"env_vars\":{\"SESSION_SECRET\":{\"type\":\"secret_text\",\"value\":\"$(openssl rand -hex 32)\"}}}}}"
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
The binding **names** matter — the worker reads `env.COMMENTS`, `env.BUNDLES`. A missing
|
|
187
|
+
KV binding makes the overlay APIs answer `{"warning":"no-kv-binding"}` instead of
|
|
188
|
+
persisting; a missing R2 binding makes publishing impossible.
|
|
189
|
+
|
|
190
|
+
`SESSION_SECRET` is not optional in practice. Session cookies are HMACs keyed on it;
|
|
191
|
+
without it the worker falls back to an unkeyed digest and sessions are not
|
|
192
|
+
cryptographically bound to anything. Set it before the first login exists.
|
|
193
|
+
|
|
194
|
+
Do **not** set `GV_ASSET_SOURCE` yet. Serving flips to the store in step 7, after the
|
|
195
|
+
store has content — flipping an empty store serves a login page at every URL.
|
|
196
|
+
|
|
197
|
+
## Step 5 — the shell repo
|
|
198
|
+
|
|
199
|
+
```bash
|
|
200
|
+
cd <parent>/<shell> || { mkdir -p <parent>/<shell> && cd <parent>/<shell>; }
|
|
201
|
+
git init -b main
|
|
202
|
+
git submodule add https://github.com/andratwiro/augur.git engine
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
`.gitmodules` URLs **must be HTTPS**. `actions/checkout` authenticates submodules by
|
|
206
|
+
rewriting HTTPS URLs with its token and cannot authenticate `git@github.com:` URLs — an
|
|
207
|
+
SSH URL here breaks every deploy at checkout.
|
|
208
|
+
|
|
209
|
+
`identity.json` — who exists. It is a roster, not a credential store: passwords live in
|
|
210
|
+
KV as PBKDF2 hashes, set by the person redeeming an invite. `[]` leaves the gate open to
|
|
211
|
+
anyone.
|
|
212
|
+
|
|
213
|
+
The one exception is the **first admin**, who has nobody to invite them. Seed that
|
|
214
|
+
account with a `passHash` — generate it with the engine's own hasher:
|
|
215
|
+
|
|
216
|
+
```bash
|
|
217
|
+
cd <parent>/augur && node --input-type=module -e "
|
|
218
|
+
const W = (await import('./src/_worker.js')).__testables;
|
|
219
|
+
console.log(await W.hashPassword(process.argv[1]));" '<your-password>'
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
```json
|
|
223
|
+
[
|
|
224
|
+
{ "email": "<admin-email>", "name": "<Name>", "initials": "XX",
|
|
225
|
+
"color": "#4a6cf7", "role": "admin", "passHash": "pbkdf2$100000$…$…" }
|
|
226
|
+
]
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
Use `passHash`, never a plaintext `pass`. A plaintext value still *resolves* as that
|
|
230
|
+
user's secret — so the account reads as active rather than pending — but nothing verifies
|
|
231
|
+
it, because password checking accepts only `pbkdf2$…` strings. The account would look
|
|
232
|
+
fine and be permanently unloginnable. Everyone after the first admin gets no password
|
|
233
|
+
field at all: invite them from the Admin panel and they choose their own.
|
|
234
|
+
|
|
235
|
+
`deploy.config.json` — instance knobs. Minimum viable:
|
|
236
|
+
|
|
237
|
+
```json
|
|
238
|
+
{
|
|
239
|
+
"siteOrigin": "<site>",
|
|
240
|
+
"spaces": [{ "id": "<space-id>", "repo": "<owner>/<space-repo>" }],
|
|
241
|
+
"shellContract": 1
|
|
242
|
+
}
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
`spaces` is a roster for repo-side automation (the health canary reads it), **not** a
|
|
246
|
+
build input — the shell mounts no space submodules. Optional keys: `realtimeOrigin`
|
|
247
|
+
(step 9), `sentinels`, `mcpHostSuffixes`, `mcpHostAllowlistUrl`, `vanityRedirects`,
|
|
248
|
+
`updateFeed`, `loginHint` (one line of plain text rendered under the
|
|
249
|
+
login form — how a demo instance surfaces its test credentials), `loginPrefill`
|
|
250
|
+
(`{email, password}` baked into the login form's `value=` attributes — how a demo
|
|
251
|
+
instance turns its throwaway account into a one-click login instead of a
|
|
252
|
+
copy-paste; leave unset and the form renders empty, as before), `devicePairing`
|
|
253
|
+
(boolean, default **`false`** — set it to `true` and this instance grows
|
|
254
|
+
`augur connect`: a terminal asks for a code, somebody types that code into a browser
|
|
255
|
+
already signed in here, and the token approval mints comes back to the terminal. No
|
|
256
|
+
password is typed into a shell, a history file or an agent transcript. It is off by
|
|
257
|
+
default because it adds an endpoint reachable WITHOUT credentials that ends in a
|
|
258
|
+
publish token, and only an explicit `true` enables it. The residual risk is
|
|
259
|
+
phishing — somebody talked into approving a code that is not theirs — so the code
|
|
260
|
+
lives five minutes, is typed rather than carried in a link, and the approval page
|
|
261
|
+
names who the terminal would publish as), `userImages`
|
|
262
|
+
(boolean, default `true` — set it to `false` and this instance stops accepting
|
|
263
|
+
user-supplied image BYTES at all: profile photos and canvas images are both refused
|
|
264
|
+
with a visible reason, while images already stored still render and a photo already
|
|
265
|
+
set can still be removed. Set it on any instance whose password is published or
|
|
266
|
+
shared with people who have agreed to nothing: the exposure is not abuse of your
|
|
267
|
+
data, it is your domain hosting somebody else's illegal image at a stable URL under
|
|
268
|
+
your name. Only an explicit `false` turns it off, so a typo cannot disable it),
|
|
269
|
+
`publishTokenTtlDays`
|
|
270
|
+
(number, default **30** — how long a publish token minted for a PERSON lives.
|
|
271
|
+
Both human doors read this one number: `augur login` and `augur connect`. When it
|
|
272
|
+
runs out the holder runs the same command again; nobody else has to do anything.
|
|
273
|
+
Tokens an admin mints by hand at `/__admin/tokens` for a machine — CI, a nightly
|
|
274
|
+
backup, an uptime probe — are NOT covered, because nothing re-runs a login for a
|
|
275
|
+
cron job and an expiry there is an outage at 4am with nobody to fix it; those end
|
|
276
|
+
on the revoke list. `0` turns expiry off. A value that is not a number falls back
|
|
277
|
+
to 30 rather than to "forever", so a typo cannot quietly restore immortal tokens.
|
|
278
|
+
Tokens minted before this existed carry no expiry and keep working — the check is
|
|
279
|
+
strictly additive, so taking this engine retires nothing anyone is using),
|
|
280
|
+
`runtimeChrome`
|
|
281
|
+
(boolean — compose current page chrome at serve time instead of trusting what
|
|
282
|
+
publishes baked in, so an engine deploy updates every page instantly; pages baked
|
|
283
|
+
by pre-marker engines still converge via the shell's space-rebake),
|
|
284
|
+
`tenantId` (the name this deployment answers to internally — leave it unset and
|
|
285
|
+
the build uses the id of the space it serves, which is what you want unless you
|
|
286
|
+
plan to rename that space and keep the identity), and `minClientProtocol`.
|
|
287
|
+
|
|
288
|
+
`minClientProtocol` is the oldest publish protocol this instance accepts a commit
|
|
289
|
+
from; a client below it is refused `426 cli-outdated` before anything is written.
|
|
290
|
+
Leave it unset on a single-operator instance — a floor nobody chose should never be
|
|
291
|
+
why a publish fails. Set it (to `3`) once several people or agents publish here from
|
|
292
|
+
clones you do not control, because an old client does not merely miss features: a
|
|
293
|
+
pre-3 client sends no `baseVersion`, so the store cannot tell whether its tree is
|
|
294
|
+
built on what is live, and a stale checkout can revert whoever published last with
|
|
295
|
+
neither of them seeing it.
|
|
296
|
+
|
|
297
|
+
`package.json` — so a local build reproduces CI's:
|
|
298
|
+
|
|
299
|
+
```json
|
|
300
|
+
{ "name": "<shell>", "private": true, "type": "module",
|
|
301
|
+
"scripts": { "build": "GV_ENGINE_ONLY=1 GV_IDENTITY_PATH=\"$PWD/identity.json\" GV_DEPLOY_CONFIG_PATH=\"$PWD/deploy.config.json\" node engine/build.js" } }
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
Workflows — copy from [templates/shell/](./templates/shell/) into
|
|
305
|
+
`.github/workflows/`, then set the two instance values in `deploy.yml`
|
|
306
|
+
(`--project-name=<pages-project>` and `AUGUR_ORIGIN: <site>`) and the origin in
|
|
307
|
+
`health.yml`:
|
|
308
|
+
|
|
309
|
+
| File | Does |
|
|
310
|
+
|---|---|
|
|
311
|
+
| `deploy.yml` | build engine chrome → Pages → publish chrome to the store. Required. |
|
|
312
|
+
| `engine-bump.yml` | take engine updates on your schedule. Required in practice. |
|
|
313
|
+
| `health.yml` | canary: pushed-but-never-published drift, stale dirty publishes. |
|
|
314
|
+
| `store-backup.yml` | off-Cloudflare copies of the store, weekly + monthly. |
|
|
315
|
+
| `kv-backup.yml` | nightly copy of KV — comments, statuses, pins, canvases, identity. |
|
|
316
|
+
| `space-preflight.yml` | probe that CI's PAT can read a space repo before you add it. |
|
|
317
|
+
| `roster-update.yml` | commit Admin-panel invites/removals back to `identity.json`. |
|
|
318
|
+
|
|
319
|
+
Take **both** backups. `store-backup.yml` covers published content in R2;
|
|
320
|
+
`kv-backup.yml` covers the state the worker keeps beside it. Neither store has
|
|
321
|
+
point-in-time restore and neither backup covers the other. Because the KV export
|
|
322
|
+
includes password hashes and live publish tokens, keep the shell repo private —
|
|
323
|
+
that is the one workflow here that is unsafe on a public one.
|
|
324
|
+
|
|
325
|
+
Commit and create the repo:
|
|
326
|
+
|
|
327
|
+
```bash
|
|
328
|
+
git add . && git commit -m "shell: initial" \
|
|
329
|
+
&& gh repo create <owner>/<shell> --private --source=. --push
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
## Step 6 — secrets, then the first deploy
|
|
333
|
+
|
|
334
|
+
```bash
|
|
335
|
+
gh secret set CLOUDFLARE_API_TOKEN -R <owner>/<shell> --body "$CF_TOKEN"
|
|
336
|
+
gh secret set CLOUDFLARE_ACCOUNT_ID -R <owner>/<shell> --body "$CF_ACCOUNT"
|
|
337
|
+
gh secret set SUBMODULE_PAT -R <owner>/<shell> --body "$GH_TOKEN"
|
|
338
|
+
gh secret set AUGUR_KV_NS -R <owner>/<shell> --body "<kv-id>" # kv-backup.yml
|
|
339
|
+
gh workflow run deploy.yml -R <owner>/<shell> && sleep 90 && curl -s <site>/_build.json
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
`SUBMODULE_PAT` needs read on the **shell itself**, not just the spaces — checkout uses
|
|
343
|
+
one token for the host repo too. If the checkout step dies with
|
|
344
|
+
`Input required and not supplied: token`, the secret is missing; if it dies fetching a
|
|
345
|
+
space repo, the PAT's repository access does not cover it (run `space-preflight.yml` to
|
|
346
|
+
probe).
|
|
347
|
+
|
|
348
|
+
Verify: `<site>` serves the login page, and signing in as `<admin-email>` with the
|
|
349
|
+
password you hashed into `passHash` lands you inside. There is no content yet — that is correct. The workflow's
|
|
350
|
+
store-publish step reports `AUGUR_TOKEN not configured — skipping` and passes; that
|
|
351
|
+
secret arrives in the next step.
|
|
352
|
+
|
|
353
|
+
## Step 7 — seed the store, then flip serving
|
|
354
|
+
|
|
355
|
+
The order matters. A flip before a seed serves a login page at every URL, because an
|
|
356
|
+
empty store means every path is unknown, and the gate answers unknown paths with the
|
|
357
|
+
login form.
|
|
358
|
+
|
|
359
|
+
```bash
|
|
360
|
+
cd <parent>/augur
|
|
361
|
+
|
|
362
|
+
# 1. Mint a publish token from your own credentials. Admins get "*" — every space.
|
|
363
|
+
AUGUR_EMAIL=<admin-email> AUGUR_PASSWORD='<your-password>' node scripts/login.mjs --origin <site>
|
|
364
|
+
|
|
365
|
+
# 2. Seed: every sibling space + the engine chrome + the instance config.
|
|
366
|
+
node scripts/publish.mjs --all
|
|
367
|
+
|
|
368
|
+
# 3. Give CI the same token, so future engine deploys can publish the chrome.
|
|
369
|
+
gh secret set AUGUR_TOKEN -R <owner>/<shell> --body "$(python3 -c \
|
|
370
|
+
"import json,os;print(json.load(open(os.path.expanduser('~/.config/augur/tokens.json')))['<site-host>']['token'])")"
|
|
371
|
+
|
|
372
|
+
# 4. Confirm the store really has manifests BEFORE flipping.
|
|
373
|
+
curl -s "$API/r2/buckets/<bucket>/objects?prefix=spaces/&per_page=50" \
|
|
374
|
+
-H "Authorization: Bearer $CF_TOKEN" | grep -c 'manifest.json'
|
|
375
|
+
|
|
376
|
+
# 5. Flip serving to the store, then redeploy so the change takes effect.
|
|
377
|
+
curl -s -X PATCH "$API/pages/projects/<pages-project>" -H "Authorization: Bearer $CF_TOKEN" \
|
|
378
|
+
-H "content-type: application/json" \
|
|
379
|
+
-d '{"deployment_configs":{"production":{"env_vars":{"GV_ASSET_SOURCE":{"type":"plain_text","value":"r2"}}}}}'
|
|
380
|
+
gh workflow run deploy.yml -R <owner>/<shell>
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
Notes on each:
|
|
384
|
+
|
|
385
|
+
- **Log in as the admin, not as anyone else.** An admin gets a `*`-scoped token; everyone
|
|
386
|
+
else gets a token scoped to the default space, and `/__publish/_instance/config`
|
|
387
|
+
rejects that with a 403. On a brand-new instance a non-admin login cannot even be
|
|
388
|
+
scoped yet, since no space has published.
|
|
389
|
+
- The token is saved to `~/.config/augur/tokens.json`, keyed by origin host, mode 0600;
|
|
390
|
+
publishing picks it up from there afterwards. Collaborators self-serve the same way
|
|
391
|
+
with `augur login` — never paste a password into a chat or a workflow file.
|
|
392
|
+
- `--all` discovers every sibling space next to the engine clone. From inside a single
|
|
393
|
+
space clone, plain `node <parent>/augur/scripts/publish.mjs` infers that one space.
|
|
394
|
+
If one parent folder holds spaces belonging to SEVERAL instances, never use `--all` —
|
|
395
|
+
it would publish every instance's spaces to this one origin. Publish each space from
|
|
396
|
+
its own clone instead.
|
|
397
|
+
- A Pages env var only applies to **new** deployments, which is why step 5 redeploys.
|
|
398
|
+
|
|
399
|
+
**The flip is one-way.** Unsetting `GV_ASSET_SOURCE` again does not roll anything back: CI
|
|
400
|
+
only ever uploads engine chrome to Pages, so assets mode on an instance built this way
|
|
401
|
+
serves a site with no content in it. To undo a bad **publish**, use the store's own
|
|
402
|
+
history — every version is kept and blobs are never collected, so any past publish is one
|
|
403
|
+
call away:
|
|
404
|
+
|
|
405
|
+
```bash
|
|
406
|
+
TOKEN=$(python3 -c "import json,os;print(json.load(open(os.path.expanduser('~/.config/augur/tokens.json')))['<site-host>']['token'])")
|
|
407
|
+
curl -s <site>/__publish/<space-id>/versions -H "Authorization: Bearer $TOKEN"
|
|
408
|
+
curl -s -X POST <site>/__publish/<space-id>/rollback -H "Authorization: Bearer $TOKEN" \
|
|
409
|
+
-H "content-type: application/json" -d '{"version":<n>}'
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
A rollback republishes the old content under a **new** version number rather than
|
|
413
|
+
repointing at the old one, so it is itself visible in the history and undone by another
|
|
414
|
+
rollback.
|
|
415
|
+
|
|
416
|
+
## Step 8 — verify
|
|
417
|
+
|
|
418
|
+
```bash
|
|
419
|
+
curl -s <site>/_build.json # builtAt fresh; spaces.<space-id>.sha == repo HEAD
|
|
420
|
+
curl -s -o /dev/null -w '%{http_code}\n' <site>/<project>/hello/ # 200 — public prototype
|
|
421
|
+
curl -s -o /dev/null -w '%{http_code}\n' <site>/__config/instance.json # 404 — config is sealed
|
|
422
|
+
curl -s <site>/__canvas/catalog.json | head -c 200 # synthesized, not stored
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
Then, in a browser: the login page appears logged out; signing in works; a comment posted
|
|
426
|
+
on a page survives a reload (that is KV); the Admin panel lists your user.
|
|
427
|
+
|
|
428
|
+
`/_build.json` is the public deploy proof — a collaborator who cannot see your CI compares
|
|
429
|
+
its `spaces.<id>.sha` to their own `git rev-parse HEAD`. `augur status` does the same
|
|
430
|
+
comparison locally across every clone.
|
|
431
|
+
|
|
432
|
+
## Step 9 — optional: custom domain
|
|
433
|
+
|
|
434
|
+
```bash
|
|
435
|
+
curl -s -X POST "$API/pages/projects/<pages-project>/domains" -H "Authorization: Bearer $CF_TOKEN" \
|
|
436
|
+
-H "content-type: application/json" -d '{"name":"<host>"}'
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
Then a **proxied CNAME `<host>` → `<pages-project>.pages.dev`** in the zone that owns the
|
|
440
|
+
domain. If the Pages token cannot see that zone — common — this is one dashboard click:
|
|
441
|
+
**HUMAN**. The domain flips to `active` on its own once the record resolves. Afterwards
|
|
442
|
+
update `siteOrigin` in `deploy.config.json`, `AUGUR_ORIGIN` in `deploy.yml` and
|
|
443
|
+
`health.yml`, and `siteOrigin` in each `space.json`.
|
|
444
|
+
|
|
445
|
+
## Step 10 — optional: canvas multiplayer
|
|
446
|
+
|
|
447
|
+
Live boards need a second worker, **one per instance** — rooms are keyed by board path, so
|
|
448
|
+
two instances sharing a worker would share rooms and board storage.
|
|
449
|
+
|
|
450
|
+
1. Copy `realtime/wrangler.example.toml` from the engine into the shell as
|
|
451
|
+
`realtime.wrangler.toml`; set `name` and point `BOARD_KV` at `<kv-id>`.
|
|
452
|
+
2. `npx wrangler deploy -c realtime.wrangler.toml` from the shell root, with the engine
|
|
453
|
+
submodule checked out and the Cloudflare credentials in the environment.
|
|
454
|
+
3. Add `"realtimeOrigin": "https://<worker>.<subdomain>.workers.dev"` to
|
|
455
|
+
`deploy.config.json`, push, redeploy.
|
|
456
|
+
4. **Required:** set the same `RT_SHARED_SECRET` on BOTH workers — a secret on the
|
|
457
|
+
realtime worker (`wrangler secret put RT_SHARED_SECRET -c realtime.wrangler.toml`)
|
|
458
|
+
and a matching environment variable on the site project — then redeploy both. The
|
|
459
|
+
realtime worker sits on a public URL while the admin-only-space seal is enforced in
|
|
460
|
+
the site worker, so this is what stops anyone who learns the hostname from joining
|
|
461
|
+
any room directly. It is not optional and it fails closed: a realtime worker with no
|
|
462
|
+
secret answers **501** to every room request rather than serving them openly.
|
|
463
|
+
|
|
464
|
+
Verify with a websocket upgrade to `/__rt?path=/__test/x` — **101**. Paths under
|
|
465
|
+
`/__test/` never persist. Without any of this, `/__rt` answers 501 and boards run
|
|
466
|
+
single-user, persisting to KV through the Pages worker. A 501 from the realtime worker
|
|
467
|
+
after wiring `realtimeOrigin` means step 4 is missing or the two values disagree. A brand-new workers.dev worker
|
|
468
|
+
can 500 (`error code: 1104`) for its first minute — retry before debugging.
|
|
469
|
+
|
|
470
|
+
## Step 11 — optional: email
|
|
471
|
+
|
|
472
|
+
Without this, an invite is a single-use link the admin copies out of the Admin panel and
|
|
473
|
+
sends however they like. That never stops working. Configuring a provider only means the
|
|
474
|
+
same link is *also* emailed to the person, and the panel tells you whether that worked.
|
|
475
|
+
|
|
476
|
+
Five runtime environment variables on the Pages project — `MAIL_API_KEY` as a secret, the
|
|
477
|
+
rest as plain values:
|
|
478
|
+
|
|
479
|
+
| variable | what it is |
|
|
480
|
+
| --- | --- |
|
|
481
|
+
| `MAIL_PROVIDER` | which driver: `scaleway` or `http`. Unset ⇒ email is off. |
|
|
482
|
+
| `MAIL_FROM` | the sending identity — `Name <noreply@example.org>`, or a bare address. |
|
|
483
|
+
| `MAIL_API_KEY` | the provider credential. **Secret**, never a plain value. |
|
|
484
|
+
| `MAIL_REGION` | `scaleway`: which region's endpoint to call. |
|
|
485
|
+
| `MAIL_PROJECT_ID` | `scaleway`: the project the sends are billed to. |
|
|
486
|
+
| `MAIL_API_URL` | `http`: where to POST. On any driver, overrides the derived endpoint. |
|
|
487
|
+
|
|
488
|
+
`http` is the escape hatch: a bearer-authenticated JSON POST of
|
|
489
|
+
`{from, fromName, to, subject, text, html, template}` to a URL you name. Put a dozen-line
|
|
490
|
+
relay in front of your own provider rather than forking the engine to add a driver.
|
|
491
|
+
|
|
492
|
+
**Send from a domain you control the DNS for, and not the one your prototypes are
|
|
493
|
+
published on.** Published content is arbitrary HTML and JavaScript, and the first
|
|
494
|
+
phishing page somebody publishes damages the reputation of the domain it was served
|
|
495
|
+
from — which must not be the reputation your invite mail depends on. Set SPF, DKIM and
|
|
496
|
+
DMARC on the sending domain, and check them with an external verifier before you trust
|
|
497
|
+
them: mail from an unauthenticated domain lands in spam, which looks exactly like an
|
|
498
|
+
invite that never arrived.
|
|
499
|
+
|
|
500
|
+
Verify by inviting yourself at a real address. Whatever happens, the panel shows the
|
|
501
|
+
link; underneath it says `Emailed to …`, or nothing at all if no provider is configured,
|
|
502
|
+
or what went wrong and that you should send the link yourself. A provider outage
|
|
503
|
+
degrades — it never blocks an invite.
|
|
504
|
+
|
|
505
|
+
## Step 12 — optional: the platform MCP proxy
|
|
506
|
+
|
|
507
|
+
A prototype talking to an upstream API usually cannot call it from the browser, so
|
|
508
|
+
`/__mcp/<host>/<path>` forwards from the site's own origin. Which hosts it forwards comes
|
|
509
|
+
from three explicit sources, and nothing else:
|
|
510
|
+
|
|
511
|
+
- `"mcpHostSuffixes": ["example.com"]` in `deploy.config.json` — any subdomain of each
|
|
512
|
+
entry.
|
|
513
|
+
- `"mcpAllowlists": ["path/in/space/allowlist.json"]` in a `space.json` — each path names
|
|
514
|
+
a JSON document the space ships, shaped `{"hosts": ["a.example"]}`, matched as **exact
|
|
515
|
+
hosts**. For platforms on a vanity domain, where no suffix rule is safe. Mounting the
|
|
516
|
+
space is the trust act. A declared list that is missing or malformed fails the build.
|
|
517
|
+
- `"mcpHostAllowlistUrl"` in `deploy.config.json` — the same document shape fetched at
|
|
518
|
+
runtime, cached an hour, for a list that lives outside any space. Unreachable → the
|
|
519
|
+
other two still apply, so a broken URL never revokes working access.
|
|
520
|
+
|
|
521
|
+
Forwarding "anything that looks like the right kind of API" would make this an open proxy
|
|
522
|
+
for whatever is reachable from the edge. Hence three explicit lists.
|
|
523
|
+
|
|
524
|
+
## Operating it
|
|
525
|
+
|
|
526
|
+
**Engine updates.** `engine-bump.yml` runs every 6 hours and on demand
|
|
527
|
+
(`gh workflow run engine-bump.yml -R <owner>/<shell>`), so an instance picks up fixes
|
|
528
|
+
without anyone watching it. It also updates the moment the engine moves if the engine
|
|
529
|
+
maintainer has your shell on their dispatch list; the schedule is what covers everyone
|
|
530
|
+
else. Detaching is deliberate — disable the workflow, or pin `TRACK` to a tag. On the default `release` track it
|
|
531
|
+
opens a PR moving the pin to the latest engine release with the notes in the body —
|
|
532
|
+
nothing lands unread. `TRACK: main` follows the bleeding edge instead. The pin is your
|
|
533
|
+
release valve: skip an update by not merging, roll back by reverting the pin commit.
|
|
534
|
+
Release mode needs one repo setting once:
|
|
535
|
+
|
|
536
|
+
```bash
|
|
537
|
+
gh api -X PUT repos/<owner>/<shell>/actions/permissions/workflow \
|
|
538
|
+
-f default_workflow_permissions=write -F can_approve_pull_request_reviews=true
|
|
539
|
+
```
|
|
540
|
+
|
|
541
|
+
**Backups.** The store is the only copy of published content and R2 has no
|
|
542
|
+
point-in-time restore. Most of it is reproducible from git at the sha `/_build.json`
|
|
543
|
+
reports — but a publish from an uncommitted working tree (`"dirty": true`) serves bytes
|
|
544
|
+
held in no repository. `store-backup.yml` takes weekly and monthly copies; on demand it is
|
|
545
|
+
`augur export --out <dir>`, and `augur restore <dir>` puts one back. Walkthrough:
|
|
546
|
+
`docs/store-recovery.md`.
|
|
547
|
+
|
|
548
|
+
⚠️ **That is published content and nothing else.** Not the roster, the invites, the
|
|
549
|
+
publish tokens, the prototype statuses, the card names, the comment threads, the boards,
|
|
550
|
+
the pins or the images pasted onto a canvas. `augur export --out <dir> --full` adds all of
|
|
551
|
+
it and `augur restore <dir> --state` replays it; `--full` needs a star-scope token because
|
|
552
|
+
the answer carries the roster and the publish-token hashes. Password hashes are never in
|
|
553
|
+
it — a credential is account-level and the export route cannot reach one.
|
|
554
|
+
|
|
555
|
+
**Drift.** `health.yml` compares each space repo's `main` HEAD against the live stamp
|
|
556
|
+
every six hours and opens a single issue when work is pushed but never published, or when
|
|
557
|
+
a dirty publish outlives its grace window. It closes the issue itself when things go
|
|
558
|
+
green.
|
|
559
|
+
|
|
560
|
+
**Adding another space later.** Create the repo (step 3, without `"default": true`), grant
|
|
561
|
+
the PAT read access, verify the grant with
|
|
562
|
+
`gh workflow run space-preflight.yml -R <owner>/<shell> -f repo=<owner>/<repo>`, add it to
|
|
563
|
+
the `spaces` roster in `deploy.config.json`, and publish from its clone. It serves under
|
|
564
|
+
`/<space-id>/`. No submodule, no CI, no pin.
|
|
565
|
+
|
|
566
|
+
**Local preview.** `npm run offline` from the engine builds every sibling space, runs the
|
|
567
|
+
real worker locally with the shell's identity, and hot-reloads in about a second — see
|
|
568
|
+
[CLAUDE.md](./CLAUDE.md). A second instance on the same machine needs `OFFLINE_PORT`. Note
|
|
569
|
+
that if `augur/.env.deploy` holds real credentials, the offline worker reads and writes the
|
|
570
|
+
**production** KV; rename it for a local-only sandbox (the startup log says which mode it
|
|
571
|
+
is in).
|
|
572
|
+
|
|
573
|
+
## Gotchas
|
|
574
|
+
|
|
575
|
+
- **Flipping `GV_ASSET_SOURCE` before the store is seeded** takes the whole site down to a
|
|
576
|
+
login page. Verify manifests exist first; roll back with the same PATCH set to `null`.
|
|
577
|
+
- **HTTPS in `.gitmodules`, always.** Checkout cannot authenticate SSH URLs.
|
|
578
|
+
- **`SUBMODULE_PAT` must cover the shell repo too**, not only the spaces.
|
|
579
|
+
- **`GITHUB_TOKEN` pushes never retrigger workflows**, by design. The bump workflows
|
|
580
|
+
therefore start `deploy.yml` explicitly, which needs `actions: write`. Anything that
|
|
581
|
+
must retrigger CI by pushing (`roster-update.yml`) needs a real PAT, `AUGUR_PIN_TOKEN`.
|
|
582
|
+
- **`/__publish/_instance/config` requires a `*`-scoped token.** A token scoped to one
|
|
583
|
+
space, or to `_engine`, gets a 403 there — this is the usual cause of a first CI publish
|
|
584
|
+
failing.
|
|
585
|
+
- **Publishing ships the working tree.** A publish from an uncommitted tree is flagged
|
|
586
|
+
`"dirty": true` in `/_build.json`; those exact bytes exist in no repository. `augur ship`
|
|
587
|
+
commits first for that reason.
|
|
588
|
+
- **A publish defines the whole space.** A clone missing a folder would remove its public
|
|
589
|
+
URLs for everyone, so `commit` refuses to drop public prefixes unless you pass
|
|
590
|
+
`--allow-unpublish`. Never work around it by passing the flag reflexively.
|
|
591
|
+
- **Never publish from a shallow clone.** Edit dates and contributor chips come from git
|
|
592
|
+
history; a `--depth 1` clone flattens both. Publish refuses, and tells you the unshallow
|
|
593
|
+
command.
|
|
594
|
+
- **A fresh Pages deploy serves mixed old and new assets for a minute or two** at some
|
|
595
|
+
edges. Poll until two consecutive fetches agree before diagnosing a "bug" reported
|
|
596
|
+
seconds after a ship.
|
|
597
|
+
- **Scheduled workflows quietly stop after ~60 idle days.** The manual dispatch always
|
|
598
|
+
works.
|
|
599
|
+
- **Do not unbind KV to escape a KV outage.** Authentication fails closed on a KV error on
|
|
600
|
+
purpose, but "no binding at all" is the offline-build case and falls through to whatever
|
|
601
|
+
`identity.json` seeds. Wait the outage out or fix the binding.
|
|
602
|
+
- **A plaintext `pass` in `identity.json` creates an unloginnable account** that reports
|
|
603
|
+
itself as active. Seed `passHash`, or nothing at all.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 the Augur contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
The MIT licence above covers the CODE. It does not grant rights in the "Augur" name, in
|
|
26
|
+
the marks under brand/, or in the third-party typefaces bundled there — those are not this
|
|
27
|
+
project's to sublicense. See TRADEMARKS.md.
|