@techgoblin/gobstack 0.4.4-beta.8 → 0.5.0-beta.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/README.md CHANGED
@@ -28,7 +28,7 @@ dependencies disagree with npm's. If you see `ERESOLVE` after a local install, r
28
28
  dependency from `package.json` and install globally instead.
29
29
 
30
30
  **The two-layer model.** The global install gives you the CLI only. `gob init` (or
31
- `gob install --target <dir> --class A`) then vendors a self-contained engine into the target
31
+ `gob install --target <dir> --class software`) then vendors a self-contained engine into the target
32
32
  repo under `.goblin/` — verifier, manifest, ban probes, skills, all of it. That second layer is
33
33
  why an initialized repo keeps working on machines with **no gobstack installed at all**: the
34
34
  engine lives in the repo, not in your `node_modules`, and `bash .goblin/bin/goblin-verify` (or a
@@ -36,7 +36,7 @@ plain `git` + `bash` box) is the only runtime the repo's gate needs.
36
36
 
37
37
  Then, from any project:
38
38
 
39
- gob install --target /path/to/repo --class A
39
+ gob install --target /path/to/repo --class software
40
40
 
41
41
  The installer writes only paths it records, hash-compares before writing, and prints `no-op` on a
42
42
  second run with the same arguments. It never overwrites `HANDOFF.md`, `AGENTS.md`, a `*-SPEC.md`,
@@ -46,6 +46,12 @@ of file it manages: `docs/CONTRACTS.md`.
46
46
  A repo that already has its own `HANDOFF.md` exits 1 on the refusal. That is the contract, not a
47
47
  failure: reconcile the file rather than forcing over it — `docs/ADOPTION.md`.
48
48
 
49
+ `--class` picks the preset: **software** (the default — shipped features, PRs, review gates),
50
+ **service** (backend jobs, config, unattended runs), **game** (playable builds, perf budgets),
51
+ **research** (specs, replays, reference corpora) and **fleet** (config-of-the-agent repos).
52
+ The single letters `A`–`E` are accepted aliases. What each preset turns on is the matrix in
53
+ `docs/ADOPTION.md`; the older names still resolve (see `docs/CONTRACTS.md`).
54
+
49
55
  After installing, in this order:
50
56
 
51
57
  cd <target> && git add -A && git commit # the install is a change like any other
@@ -53,7 +59,7 @@ After installing, in this order:
53
59
  gob emit --platform <p> # optional, per platform: the agent skills are an opt-in
54
60
  gob audit # once, deliberately: the ONLY network step (SC-07)
55
61
 
56
- **A default class-A install (no agent skills — those are `gob emit`'s job) verifies green —
62
+ **A default software-class install (no agent skills — those are `gob emit`'s job) verifies green —
57
63
  `38 passed, 0 failed, 11 advisory, 33 skipped`, exit 0 — once
58
64
  `HANDOFF.md` names a commit that exists. Before that edit the scaffold's `0000000` placeholder is
59
65
  the one expected red: `37 passed, 1 failed`, `HP-05`. Both numbers measured at W6 (neutral-first);
@@ -91,7 +97,7 @@ The measurement and the vacuous-pass reading are in `docs/CONTRACTS.md`.
91
97
  | `gob upgrade` | migrate a repo to the shared global engine at `~/.goblin/engine` — 8 steps, two commits, one report |
92
98
  | `gob doctor` | one run across the platforms below: DETECTED / NOT-DETECTED / DRIFT per platform |
93
99
  | `gob emit` | write the skills + context block for one platform (`--scope project` or `global`); `--unshadow` removes a hermes project skill whose hash equals the source |
94
- | `gob init` | the first-run wizard: detect → class → branch/email → first gate → emit → verify, one screen per question; every question has a flag (`--class app --branch main --email a@b.c --gate 'cmd' --emit hermes`), so CI runs it with zero prompts; `--dry-run` prints the plan and writes nothing |
100
+ | `gob init` | the first-run wizard: detect → class → branch/email → first gate → emit → verify, one screen per question; every question has a flag (`--class software --branch main --email a@b.c --gate 'cmd' --emit hermes`), so CI runs it with zero prompts; `--dry-run` prints the plan and writes nothing |
95
101
 
96
102
  `goblin` remains as a legacy alias for every command above — existing scripts keep working, but
97
103
  new commands and docs use `gob`.
@@ -145,9 +151,9 @@ below is the reference material the guide points into, so the two do not compete
145
151
  | `docs/CONTRACTS.md` | the installer/verifier interface, exit codes, idempotency, uninstall |
146
152
  | `docs/INTEGRATION.md` | the board, cron, the skills precedence order, the referenced standard |
147
153
  | `docs/RISKS.md` | the risk register, the advisory rows named, the non-goals |
148
- | `docs/CI.md` | the CI lane: what makes a workflow a gate, the four settings a repository cannot set, and the desktop-shell class |
154
+ | `docs/CI.md` | the CI lane: what makes a workflow a gate, the four settings a repository cannot set, and the electron opt-in |
149
155
  | `docs/LOOP.md` | the judge role and the loop contract: what a goal-mode loop actually does, the record, and what neither can see |
150
- | `docs/ADOPTION.md` | the six classes, the preset matrix, the adoption order |
156
+ | `docs/ADOPTION.md` | the five classes, the preset matrix, the adoption order |
151
157
  | `docs/LIMITS.md` | where this is weaker than its sources, and what is unproven |
152
158
 
153
159
  `manifest/enforcement.tsv` is the source of truth for rules; `manifest/classes.tsv` for what a
@@ -166,7 +172,7 @@ run · `3` the manifest is broken. Every run prints what it cannot see.
166
172
  bash tests/run-tests.sh
167
173
 
168
174
  Runs the source-scope rules (PR-01..PR-05) and the test scripts, including `t-verify-red.sh` —
169
- one control per target-scope row (167 over 82 target rows), each required to go RED and then
175
+ one control per target-scope row (168 over 82 target rows), each required to go RED and then
170
176
  restored, plus `t-audit.sh` for the SC-07 producer. **A verifier that only ever prints GREEN is a
171
177
  failure**, so that file is the one that matters most.
172
178
 
package/VERSION CHANGED
@@ -1 +1 @@
1
- 0.4.4
1
+ 0.5.0
package/bin/goblin-audit CHANGED
@@ -23,7 +23,7 @@
23
23
  # because an empty record reads to SC-07 as "clean" and that would be a fabricated pass
24
24
  set -uo pipefail
25
25
 
26
- GOBLIN_AUDIT_VERSION="0.4.4"
26
+ GOBLIN_AUDIT_VERSION="0.5.0"
27
27
 
28
28
  usage() { sed -n '2,/^$/p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//'; }
29
29
 
package/bin/goblin-bans CHANGED
@@ -28,7 +28,7 @@
28
28
 
29
29
  set -uo pipefail
30
30
 
31
- GOBLIN_BANS_VERSION="0.4.4"
31
+ GOBLIN_BANS_VERSION="0.5.0"
32
32
  SELF_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
33
33
  # shellcheck source=goblin-lib.sh
34
34
  . "$SELF_DIR/goblin-lib.sh"
@@ -88,6 +88,14 @@ if [ -f "$CONFIG" ]; then
88
88
  ENABLED=$(g_yaml_scalar "$CONFIG" bans)
89
89
  ENABLED=${ENABLED#[}; ENABLED=${ENABLED%]}
90
90
  ENABLED=$(printf '%s' "$ENABLED" | tr ',' ' ' | tr -s ' ' ' ')
91
+ # The electron opt-in (software class): `electron: true` turns the electron ban set ON even
92
+ # when a hand-edited bans: list omits it — the declaration is the contract, and the engine
93
+ # (not the LLM) is what makes a listed ban non-skippable. A repo without the key is unaffected.
94
+ if [ "$(g_yaml_scalar "$CONFIG" electron)" = "true" ]; then
95
+ for e in BN-06 BN-07 BN-08 BN-09; do
96
+ case " $ENABLED " in *" $e "*) ;; *) ENABLED="$ENABLED $e" ;; esac
97
+ done
98
+ fi
91
99
  fi
92
100
  enabled() {
93
101
  [ -z "$ENABLED" ] && return 1
package/bin/goblin-init CHANGED
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env bash
2
2
  # goblin-init — the W6 first-run wizard (`gob init`).
3
3
  #
4
- # gob init [--target <dir>] [--class <app|service|game|research|agent|desktop|A..F>]
5
- # [--branch <name>] [--email <addr>] [--gate <cmd>]
4
+ # gob init [--target <dir>] [--class <software|service|game|research|fleet|A..E|app|agent|desktop>]
5
+ # [--electron] [--branch <name>] [--email <addr>] [--gate <cmd>]
6
6
  # [--emit <p[,p..]>] [--scope project|global] [--yes] [--dry-run]
7
7
  #
8
8
  # One screen per question, answered steps collapsing into the ✔/◆/○ rail above. On a tty
@@ -143,6 +143,7 @@ ask_value() {
143
143
 
144
144
  # ------------------------------------------------------------------ flags -----
145
145
  TARGET="" CLASS="" BRANCH="" EMAIL="" GATE="" EMIT="" SCOPE=""
146
+ ELECTRON=0
146
147
  YES=0; DRYRUN=0
147
148
  usage() { sed -n '2,/^$/p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//'; }
148
149
 
@@ -150,6 +151,7 @@ while [ $# -gt 0 ]; do
150
151
  case "$1" in
151
152
  --target) TARGET="${2:-}"; shift 2 ;;
152
153
  --class) CLASS="${2:-}"; shift 2 ;;
154
+ --electron) ELECTRON=1; shift ;;
153
155
  --branch) BRANCH="${2:-}"; shift 2 ;;
154
156
  --email) EMAIL="${2:-}"; shift 2 ;;
155
157
  --gate) GATE="${2:-}"; shift 2 ;;
@@ -255,45 +257,45 @@ if [ -z "$CLASS" ]; then
255
257
  if [ "$TTY_IN" -eq 1 ]; then
256
258
  rail_say ""
257
259
  rail_say " ${C_MUTED}what kind of work does this repo do?$C_RESET"
258
- rail_say " $C_TEXT 1 ▸ app shipped features, PRs, review gates ${C_DIM}(class A)$C_RESET"
260
+ rail_say " $C_TEXT 1 ▸ software shipped features, PRs, review gates ${C_DIM}(class A)$C_RESET"
259
261
  rail_say " $C_TEXT 2 service backend jobs, config, unattended runs ${C_DIM}(class B)$C_RESET"
260
262
  rail_say " $C_TEXT 3 game playable builds, perf budgets ${C_DIM}(class C)$C_RESET"
261
263
  rail_say " $C_TEXT 4 research specs, replays, reference corpora ${C_DIM}(class D)$C_RESET"
262
- rail_say " $C_TEXT 5 agent fleets, loops, unattended automation ${C_DIM}(class E)$C_RESET"
263
- rail_say " $C_TEXT 6 desktop the shell you live in, perf budget ${C_DIM}(class F)$C_RESET"
264
- rail_say " ${C_MUTED}↑ number + Enter · Enter = app · the class letter works too (A-F)$C_RESET"
264
+ rail_say " $C_TEXT 5 fleet fleets, loops, unattended automation ${C_DIM}(class E)$C_RESET"
265
+ rail_say " ${C_MUTED}↑ number + Enter · Enter = software · a class letter also works (A-E)$C_RESET"
266
+ rail_say " ${C_DIM}an Electron desktop shell: pick software, then --electron$C_RESET"
265
267
  printf ' '
266
268
  read -r REPLY_CLASS
267
269
  ASKED_ANY=1
268
270
  rail_say ""
269
271
  else
270
272
  REPLY_CLASS="1"
271
- cascade "class: app (default; --class app|service|game|research|agent|desktop or A-F)"
273
+ cascade "class: software (default; --class software|service|game|research|fleet or A-E)"
272
274
  fi
273
275
  case "$REPLY_CLASS" in
274
- 1) CLASS="app" ;; 2) CLASS="service" ;; 3) CLASS="game" ;;
275
- 4) CLASS="research" ;; 5) CLASS="agent" ;; 6) CLASS="desktop" ;;
276
- *) CLASS="${REPLY_CLASS:-app}" ;;
276
+ 1) CLASS="software" ;; 2) CLASS="service" ;; 3) CLASS="game" ;;
277
+ 4) CLASS="research" ;; 5) CLASS="fleet" ;;
278
+ *) CLASS="${REPLY_CLASS:-software}" ;;
277
279
  esac
278
280
  fi
279
- # Normalise every accepted spelling to the letter goblin-install requires. Domain names,
280
- # the class letter, and a lowercase letter all resolve; anything else is refused here,
281
- # with the full enum, rather than deep inside the installer.
282
- case "$CLASS" in
283
- app|A|a) CLASS="A" ;;
284
- service|B|b) CLASS="B" ;;
285
- game|C|c) CLASS="C" ;;
286
- research|D|d) CLASS="D" ;;
287
- agent|E|e) CLASS="E" ;;
288
- desktop|F|f) CLASS="F" ;;
289
- *)
290
- g_err "--class must be app|service|game|research|agent|desktop (or A-F), got '$CLASS'"
291
- exit 2 ;;
281
+ # Resolve every accepted spelling to the canonical class NAME. The letters A-E and the old
282
+ # taught names (app/agent/desktop) are read-time aliases; anything else is refused here, with
283
+ # the full enum, rather than deep inside the installer. `desktop`/F means the OLD desktop
284
+ # install, so it also sets the electron opt-in.
285
+ g_class_is_electron_alias "$CLASS" && ELECTRON=1
286
+ CLASS_RAW="$CLASS"
287
+ CLASS=$(g_class_canon "$CLASS") \
288
+ || { g_err "--class must be software|service|game|research|fleet (or A-E / app / agent / desktop), got '$CLASS_RAW'"; exit 2; }
289
+ case "$CLASS_RAW" in
290
+ app|agent|desktop) g_info "note: '$CLASS_RAW' is now called '$CLASS' (accepted as an alias)";;
292
291
  esac
293
- CLASS_NAME=$(ls "$SRC"/presets/"$CLASS"-*.yaml 2>/dev/null | head -n 1)
294
- STEP_VAL[1]="$C_TEXT$CLASS ($([ -n "$CLASS_NAME" ] && basename "$CLASS_NAME" .yaml | cut -d- -f2-))$C_RESET"
292
+ if [ "$ELECTRON" -eq 1 ]; then
293
+ g_info "electron: on (the desktop opt-in — the app_bundle_bytes ratchet, BN-06..09 bans, a host gate)"
294
+ fi
295
+ CLASS_NAME="$SRC/presets/$CLASS.yaml"
296
+ STEP_VAL[1]="$C_TEXT$CLASS$C_RESET"
295
297
  if [ "$TTY_OUT" -eq 1 ]; then
296
- rail_say " ✔ $((CURRENT - 1)). class $C_TEXT$CLASS$([ -n "$CLASS_NAME" ] && printf ' — %s' "$(basename "$CLASS_NAME" .yaml | cut -d- -f2-)")$C_RESET"
298
+ rail_say " ✔ $((CURRENT - 1)). class $C_TEXT$CLASS$C_RESET"
297
299
  else
298
300
  cascade "class: $CLASS"
299
301
  fi
@@ -460,11 +462,15 @@ fi
460
462
  # never rewinds past its own buffer by counting lines it did not print.
461
463
  if [ "$TTY_OUT" -eq 1 ]; then
462
464
  rail_note ""
463
- rail_note " ${C_MUTED}running: goblin-install --target $TARGET --class $CLASS${C_RESET}"
465
+ rail_note " ${C_MUTED}running: goblin-install --target $TARGET --class $CLASS$([ "$ELECTRON" -eq 1 ] && printf ' --electron')${C_RESET}"
466
+ else
467
+ printf 'gob init [run] goblin-install --target %s --class %s%s\n' "$TARGET" "$CLASS" "$([ "$ELECTRON" -eq 1 ] && printf ' --electron')"
468
+ fi
469
+ if [ "$ELECTRON" -eq 1 ]; then
470
+ bash "$SRC/bin/goblin-install" --target "$TARGET" --class "$CLASS" --electron --skills no --yes
464
471
  else
465
- printf 'gob init [run] goblin-install --target %s --class %s\n' "$TARGET" "$CLASS"
472
+ bash "$SRC/bin/goblin-install" --target "$TARGET" --class "$CLASS" --skills no --yes
466
473
  fi
467
- bash "$SRC/bin/goblin-install" --target "$TARGET" --class "$CLASS" --skills no --yes
468
474
  EXIT_INSTALL=$?
469
475
  if [ "$EXIT_INSTALL" -ne 0 ]; then
470
476
  g_err "install failed (exit $EXIT_INSTALL) — the refusals above name the path and the fix"
@@ -3,7 +3,11 @@
3
3
  #
4
4
  # Usage: gob install --target <dir> [options] (script: bin/goblin-install)
5
5
  # --target <dir> required; the repo root to install into
6
- # --class A|B|C|D|E|F required unless --uninstall or --re-pin; anything else needs an explicit class
6
+ # --class <name> required unless --uninstall or --re-pin; one of
7
+ # software|service|game|research|fleet, or the aliases A-E / app / agent
8
+ # / desktop (desktop/F => software + --electron); anything else is refused
9
+ # --electron the electron opt-in overlay over the software class (electron: true):
10
+ # the app_bundle_bytes ratchet, BN-06..09 bans, the host-gate declaration
7
11
  # --models <path> model mapping file (default: $GOBLIN_MODELS -> ~/projects/fleet-model.yaml)
8
12
  # --practice <path> the referenced standard (default: $GOBLIN_PRACTICE -> ~/projects/PROJECT-PRACTICE.md); a named path that is absent is reported, never silently dropped
9
13
  # --parts <list> comma list to install; default = every part the class requires
@@ -23,7 +27,7 @@
23
27
 
24
28
  set -uo pipefail
25
29
 
26
- GOBLIN_INSTALL_VERSION="0.4.4"
30
+ GOBLIN_INSTALL_VERSION="0.5.0"
27
31
  SELF_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
28
32
  SRC=$(cd "$SELF_DIR/.." && pwd)
29
33
  # shellcheck source=goblin-lib.sh
@@ -57,6 +61,10 @@ UNINSTALL=0
57
61
  RE_PIN=0
58
62
  FORCE=0
59
63
  OPT_OUT=""
64
+ # The electron opt-in (the merged desktop/F class): renders presets/electron-overlay.yaml over
65
+ # the software preset. Auto-set when --class resolves via the desktop/F/f alias, so the alias
66
+ # behaves as the old class F did rather than silently installing weaker software.
67
+ ELECTRON=0
60
68
 
61
69
  usage() {
62
70
  # The header comment runs from line 2 to the first blank line, so adding an option cannot
@@ -73,6 +81,7 @@ while [ $# -gt 0 ]; do
73
81
  --practice) PRACTICE="${2:-}"; PRACTICE_ARG="$PRACTICE"; shift 2 ;;
74
82
  --parts) PARTS="${2:-}"; shift 2 ;;
75
83
  --archive) ARCHIVE="true"; shift ;;
84
+ --electron) ELECTRON=1; shift ;;
76
85
  --skills) SKILLS="${2:-}"; SKILLS_NAMED=1; shift 2 ;;
77
86
  --dry-run) DRY_RUN=1; shift ;;
78
87
  --upgrade) UPGRADE=1; shift ;;
@@ -239,8 +248,14 @@ if [ "$RE_PIN" -eq 1 ]; then
239
248
  exit 0
240
249
  fi
241
250
 
242
- [ -n "$CLASS" ] || { g_err "--class is required (A|B|C|D|E|F)"; exit 2; }
243
- case "$CLASS" in A|B|C|D|E|F) ;; *) g_err "--class must be one of A B C D E F, got '$CLASS'"; exit 2 ;; esac
251
+ [ -n "$CLASS" ] || { g_err "--class is required (software|service|game|research|fleet, or A-E / app / agent / desktop)"; exit 2; }
252
+ # Resolve every accepted spelling to the canonical class NAME before anything reads it. The
253
+ # letters A-E and the old names app/agent/desktop are read-time aliases; desktop/F/f also sets
254
+ # the electron opt-in so the merged class behaves as the old class F did.
255
+ g_class_is_electron_alias "$CLASS" && ELECTRON=1
256
+ CLASS_RAW="$CLASS"
257
+ CLASS=$(g_class_canon "$CLASS") \
258
+ || { g_err "--class must be software|service|game|research|fleet (or A-E / app / agent / desktop), got '$CLASS_RAW'"; exit 2; }
244
259
  case "$SKILLS" in yes|no) ;; *) g_err "--skills must be yes or no"; exit 2 ;; esac
245
260
 
246
261
  # W6 migration safety: on a --upgrade (or any re-install) of a repo whose record shows skills
@@ -257,10 +272,24 @@ if [ -f "$INSTALLED" ] && [ "$SKILLS_NAMED" -eq 0 ]; then
257
272
  fi
258
273
  fi
259
274
 
260
- PRESET=$(ls "$SRC"/presets/"$CLASS"-*.yaml 2>/dev/null | head -n 1)
261
- [ -n "$PRESET" ] || { g_err "no preset for class $CLASS"; exit 2; }
275
+ PRESET="$SRC/presets/$CLASS.yaml"
276
+ [ -f "$PRESET" ] || { g_err "no preset for class $CLASS ($PRESET)"; exit 2; }
262
277
 
263
- preset() { local v; v=$(g_yaml_scalar "$PRESET" "$1"); printf '%s' "${v%\"}" | sed 's/^"//'; }
278
+ # The electron overlay (the merged desktop/F class): every key it declares — including an empty
279
+ # one like sec_write_routes: "" — wins over the class preset; an absent key falls through. This
280
+ # is config, not a second code path: the same preset() reader, one layer up.
281
+ OVERLAY=""
282
+ if [ "$ELECTRON" -eq 1 ]; then
283
+ OVERLAY="$SRC/presets/electron-overlay.yaml"
284
+ [ -f "$OVERLAY" ] || { g_err "no electron overlay at $OVERLAY"; exit 2; }
285
+ fi
286
+ overlay_has() { [ -n "$OVERLAY" ] && grep -qE "^$1:" "$OVERLAY"; }
287
+
288
+ preset() {
289
+ local v
290
+ if overlay_has "$1"; then v=$(g_yaml_scalar "$OVERLAY" "$1"); else v=$(g_yaml_scalar "$PRESET" "$1"); fi
291
+ printf '%s' "${v%\"}" | sed 's/^"//'
292
+ }
264
293
 
265
294
  HARNESS_DIR=$(preset harness_dir); [ -n "$HARNESS_DIR" ] || HARNESS_DIR=checks
266
295
  SCAFFOLD=$(preset scaffold_checks); [ -n "$SCAFFOLD" ] || SCAFFOLD=no
@@ -488,6 +517,7 @@ render_config() {
488
517
  runtime=" - $RUNTIME_DATA"
489
518
  render "$SRC/templates/goblin.yaml.tmpl" \
490
519
  CLASS "$CLASS" \
520
+ ELECTRON "$( [ "$ELECTRON" -eq 1 ] && printf 'true' || printf 'false' )" \
491
521
  BRANCH "$BRANCH" \
492
522
  MODELS_FILE "$MODELS" \
493
523
  PRACTICE "$PRACTICE" \
package/bin/goblin-lib.sh CHANGED
@@ -16,7 +16,7 @@
16
16
  # - name: typecheck four-space-indented second member of a list entry
17
17
  # cmd: npx tsc --noEmit
18
18
 
19
- GOBLIN_LIB_VERSION="0.4.4"
19
+ GOBLIN_LIB_VERSION="0.5.0"
20
20
 
21
21
  # ---------------------------------------------------------------- output -----
22
22
  g_pass() { printf 'PASS %-6s %s\n' "$1" "$2"; }
@@ -185,6 +185,33 @@ g_part_disabled() {
185
185
  }
186
186
 
187
187
  # ------------------------------------------------------------- class data ----
188
+ # g_class_canon <spelling> -> the canonical class NAME
189
+ # software | service | game | research | fleet
190
+ # The taxonomy is five domain-named classes. The letters A-E and the older taught domain names
191
+ # (app, agent, desktop) stay as READ-TIME aliases so every existing installed.json / goblin.yaml
192
+ # - which record a letter or an old name - keeps verifying with no rewrite. `desktop` / `F` / `f`
193
+ # resolve to `software`: F was merged into A (their classes.tsv need columns are identical), and
194
+ # what made a desktop shell different is the `electron:` opt-in + ban list, config keys the repo
195
+ # already carries. Unknown spelling -> empty output; the caller refuses with the enum.
196
+ g_class_canon() {
197
+ case "$1" in
198
+ software|A|a|app) printf 'software' ;;
199
+ service|B|b) printf 'service' ;;
200
+ game|C|c) printf 'game' ;;
201
+ research|D|d) printf 'research' ;;
202
+ fleet|E|e|agent) printf 'fleet' ;;
203
+ desktop|F|f) printf 'software' ;;
204
+ *) return 1 ;;
205
+ esac
206
+ }
207
+
208
+ # g_class_is_electron_alias <spelling> -> 0 when the spelling is the merged desktop/F spelling.
209
+ # `--class desktop` (or F/f) must mean the OLD desktop install, not a silently weaker software
210
+ # one: the installer auto-sets electron: true so the alias behaves as F did.
211
+ g_class_is_electron_alias() {
212
+ case "$1" in desktop|F|f) return 0 ;; *) return 1 ;; esac
213
+ }
214
+
188
215
  # g_class_need <classes.tsv> <class> <part> -> R | O | -
189
216
  g_class_need() {
190
217
  awk -F'\t' -v c="$2" -v p="$3" '
@@ -30,7 +30,7 @@
30
30
  # `git revert` of this command's own two commits (W3-SPEC §5).
31
31
  set -uo pipefail
32
32
 
33
- GOBLIN_UPGRADE_VERSION="0.4.4"
33
+ GOBLIN_UPGRADE_VERSION="0.5.0"
34
34
 
35
35
  usage() { sed -n '2,/^$/p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//'; }
36
36
 
package/bin/goblin-verify CHANGED
@@ -19,7 +19,7 @@
19
19
 
20
20
  set -uo pipefail
21
21
 
22
- GOBLIN_VERIFY_VERSION="0.4.4"
22
+ GOBLIN_VERIFY_VERSION="0.5.0"
23
23
  SELF_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
24
24
  # shellcheck source=goblin-lib.sh
25
25
  . "$SELF_DIR/goblin-lib.sh"
@@ -261,15 +261,17 @@ if [ -n "$ONLY" ]; then
261
261
  fi
262
262
 
263
263
  if [ ! -f "$CONFIG" ]; then
264
- g_err "not installed: $CONFIG is absent. Run: goblin-install --target $ROOT --class <A|B|C|D|E>"
264
+ g_err "not installed: $CONFIG is absent. Run: goblin-install --target $ROOT --class <software|service|game|research|fleet>"
265
265
  exit 2
266
266
  fi
267
267
 
268
- CLASS=$(g_yaml_scalar "$CONFIG" class)
269
- case "$CLASS" in
270
- A|B|C|D|E|F) ;;
271
- *) g_err "unknown class '${CLASS:-<empty>}' in $CONFIG (expected A|B|C|D|E|F)"; exit 2 ;;
272
- esac
268
+ # The class is read-time resolved: goblin.yaml records the canonical name on new installs, but
269
+ # every repo installed before the 5-class merge records a letter (A..F) or an old name
270
+ # (app/agent/desktop). g_class_canon maps them all to the five canonical names; the tsv carries
271
+ # the canonical names, so nothing else in this file has to know about letters. `desktop`/F -> software.
272
+ CLASS_RAW=$(g_yaml_scalar "$CONFIG" class)
273
+ CLASS=$(g_class_canon "$CLASS_RAW") \
274
+ || { g_err "unknown class '${CLASS_RAW:-<empty>}' in $CONFIG (expected software|service|game|research|fleet, or A-E / app / agent / desktop)"; exit 2; }
273
275
 
274
276
  # Root vs git toplevel. find_installed_root resolves the CONFIG lookup correctly, but the
275
277
  # rows that shell out to git run in $ROOT and git resolves that to the ENCLOSING repo when
@@ -374,8 +376,12 @@ PERF_BASELINE_COMMIT=$(g_unquote "$(g_yaml_block_scalar "$CONFIG" perf baseline_
374
376
  PERF_BASELINE_VALUE=$(g_yaml_block_scalar "$CONFIG" perf baseline_value)
375
377
  PERF_MEASURED=$(g_unquote "$(g_yaml_block_scalar "$CONFIG" perf measured)")
376
378
  PERF_HOST_GATE=$(g_yaml_block_scalar "$CONFIG" perf host_gate)
379
+ # The electron opt-in (software class): true means the electron ban set stays on regardless of a
380
+ # hand-edited bans: list, and a perf host gate must be declared (PF-01). Absent on a repo
381
+ # installed before the merge — read as off, so a legacy class-F repo verifies with no rewrite.
382
+ ELECTRON=$(g_yaml_scalar "$CONFIG" electron)
377
383
  export SEC_GITIGNORE_FAMILY SEC_BUILD_OUTPUT SEC_AUDIT_CMD SEC_AUDIT_MAX_AGE SEC_WAIVER_MAX_AGE
378
- export SEC_WRITE_ROUTES PERF_METRIC PERF_CMD PERF_BASELINE_VALUE PERF_HOST_GATE
384
+ export SEC_WRITE_ROUTES PERF_METRIC PERF_CMD PERF_BASELINE_VALUE PERF_HOST_GATE ELECTRON
379
385
 
380
386
  # Whole days between an ISO date and today, or empty when the date does not parse on this host
381
387
  # (`date -d` is GNU; the row FAILS rather than assuming, because an unreadable date is not a
@@ -613,8 +619,14 @@ check_hp_03() {
613
619
  m = split(names, dn, " ")
614
620
  for (i = 1; i <= m; i++) if (dn[i] != "") decl[dn[i]] = 1
615
621
  }
616
- tolower($0) ~ /^#{2,3}[[:space:]]+[^[:alpha:]]*gates?([^[:alpha:]]|$)/ { g = 1; next }
617
- g && /^#{1,3}[[:space:]]/ { g = 0 }
622
+ # Portability (clean-room catch, 2026-10-05): the Debian default awk (mawk) has no
623
+ # interval expressions ({2,3}) — the header rule matched nothing and HP-03 went red on
624
+ # every fresh Debian install. The hash-header variants are spelled out as two pattern
625
+ # statements, and the date as repeated [0-9] groups. (No apostrophes here: this
626
+ # program is a single-quoted shell string.)
627
+ tolower($0) ~ /^###[[:space:]]+[^[:alpha:]]*gates?([^[:alpha:]]|$)/ { g = 1; next }
628
+ tolower($0) ~ /^##[[:space:]]+[^[:alpha:]]*gates?([^[:alpha:]]|$)/ { g = 1; next }
629
+ g && (/^###[[:space:]]/ || /^##[[:space:]]/ || /^#[[:space:]]/) { g = 0 }
618
630
  !g { next }
619
631
  tolower($0) ~ /example of the required form/ { next }
620
632
  $0 !~ /=/ { next }
@@ -624,7 +636,7 @@ check_hp_03() {
624
636
  if (!hit) for (k in decl) if (hasname($0, k)) { hit = 1; break }
625
637
  if (hit) {
626
638
  n++
627
- if ($0 !~ /measured [0-9]{4}-[0-9]{2}-[0-9]{2}/) {
639
+ if ($0 !~ /measured [0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]/) {
628
640
  print "a gate line carries no measured date: " $0
629
641
  bad = 1
630
642
  }
@@ -1594,6 +1606,13 @@ check_pf_01() {
1594
1606
  # to ignore it.
1595
1607
  local rname bad=0
1596
1608
  rname=$(g_yaml_block_scalar "$CONFIG" ratchet name)
1609
+ # The electron opt-in's done-definition: an Electron repo declares its host gate (the FPS /
1610
+ # main_thread_busy_pct number a hermetic rule cannot measure). `electron: true` with no host
1611
+ # gate is a repo missing half its definition — FAIL, before the baseline skip below can mask it.
1612
+ if [ "$ELECTRON" = "true" ] && [ -z "$PERF_HOST_GATE" ]; then
1613
+ printf 'electron: true requires a declared perf.host_gate — declare the host-gate probe (the number no hermetic rule can measure) or drop electron: true\n'
1614
+ return 1
1615
+ fi
1597
1616
  [ -n "$PERF_METRIC" ] || {
1598
1617
  printf 'this class declares no perf metric (perf.metric is empty). The ratchet still carries\n%s; declare metric/cmd here the day you pick one\n' "${rname:-nothing}"
1599
1618
  return 3
package/docs/ADOPTION.md CHANGED
@@ -1,26 +1,25 @@
1
1
  # Adoption — classes, presets, and the order
2
2
 
3
- ## The six classes
3
+ ## The five classes
4
4
 
5
5
  A class is **not** a stringency level. It selects which parts are required, optional or off, and
6
6
  it supplies the default gate and ratchet shape. The gate vocabulary differs by class; the
7
- harness does not.
7
+ harness does not. The names are words; the letters `A`..`E` are read-time aliases.
8
8
 
9
9
  | Class | What "done" means |
10
10
  |---|---|
11
- | **A. Shipped software** | a gate set reports measured numbers, a round lands, the artifact deploys or publishes |
12
- | **B. Service / configuration** | a contract (schema, route, API) is unchanged, or the change is intentional and migrated |
13
- | **C. Game** | a suite green in the Editor **and** a human feel verdict — the verdict is a first-class deliverable |
14
- | **D. Knowledge / research** | a question is answered with sources and the answer is findable |
15
- | **E. Agent-fleet config** | a config change is applied, verified against the **artifact**, and versioned |
16
- | **F. Desktop shell** | the renderer is isolated from Node, the main process is not busy, and the packaged bundle ships no dev dependency |
11
+ | **software** (A) | a gate set reports measured numbers, a round lands, the artifact deploys or publishes |
12
+ | **service** (B) | a contract (schema, route, API) is unchanged, or the change is intentional and migrated |
13
+ | **game** (C) | a suite green in the Editor **and** a human feel verdict — the verdict is a first-class deliverable |
14
+ | **research** (D) | a question is answered with sources and the answer is findable |
15
+ | **fleet** (E) | a config change is applied, verified against the **artifact**, and versioned |
17
16
 
18
17
  Two placements worth arguing about:
19
18
 
20
19
  - A repo whose code is small and lives elsewhere, while the repo holds *output*, belongs in
21
- **D**, not A — gating it like an application gates the wrong artifact; its gate is freshness,
22
- not compilation.
23
- - An input directory that is not a build target at all belongs in **D** with **`--archive`**.
20
+ **research**, not **software** — gating it like an application gates the wrong artifact; its gate
21
+ is freshness, not compilation.
22
+ - An input directory that is not a build target at all belongs in **research** with **`--archive`**.
24
23
  Without the flag the installer keeps producing HANDOFFs for a directory whose own design
25
24
  folders are empty.
26
25
 
@@ -29,25 +28,28 @@ Two placements worth arguing about:
29
28
  `R` = required · `O` = optional (installed, reported) · `—` = off. The same data is in
30
29
  `manifest/classes.tsv`, and `CL-01` checks it against the repo.
31
30
 
32
- | Part | A | B | C | D | E | F |
33
- |---|---|---|---|---|---|---|
34
- | HANDOFF | R | R | R | R | R | R |
35
- | SPEC before change | R | R | R | — | R | R |
36
- | Verification gate | R | R | R | O | R | R |
37
- | Pinned-commit REPLAY | R | — | R | — | O | R |
38
- | Ratchet | R | O | O | — | O | R |
39
- | PR gate | O | — | O | — | O | O |
40
- | Review panel | O | — | R | — | O | O |
41
- | Playbooks (the skills) | R | R | R | R | R | R |
42
- | Design tokens | O | — | — | — | — | O |
43
- | CI lane | R | — | O | — | O | R |
44
-
45
- **F is the desktop shell**, added at W4: it declares the Electron failure surface as bans
46
- (`BN-06`..`BN-09`) and declares its FPS number as a **host gate** rather than a ratchet, because
47
- the probe that measures it needs Playwright or Electron plus a display — neither of which a
48
- shipped rule may depend on. Its ratchet carries `app_bundle_bytes` instead. `docs/CI.md` §3 argues
49
- that in full, including why frame time is the wrong number (measured flat at 16.70 ms while the
50
- main thread went from 1.8 % to 54.5 % busy).
31
+ | Part | software | service | game | research | fleet |
32
+ |---|---|---|---|---|---|
33
+ | HANDOFF | R | R | R | R | R |
34
+ | SPEC before change | R | R | R | — | R |
35
+ | Verification gate | R | R | R | O | R |
36
+ | Pinned-commit REPLAY | R | — | R | — | O |
37
+ | Ratchet | R | O | O | — | O |
38
+ | PR gate | O | — | O | — | O |
39
+ | Review panel | O | — | R | — | O |
40
+ | Playbooks (the skills) | R | R | R | R | R |
41
+ | Design tokens | O | — | — | — | O |
42
+ | CI lane | R | — | O | — | O |
43
+
44
+ **The electron opt-in, not a class**, added at W4: an Electron app is the **software** class with
45
+ `electron: true`, which declares the Electron failure surface as bans (`BN-06`..`BN-09`) and
46
+ declares its FPS number as a **host gate** rather than a ratchet, because the probe that measures
47
+ it needs Playwright or Electron plus a display — neither of which a shipped rule may depend on. Its
48
+ ratchet carries `app_bundle_bytes` instead. The old sixth class was merged into `software` (its need
49
+ column measured identical on all ten parts), and the old `F` letter remains an install-time alias
50
+ that selects `software` **with** the opt-in. `docs/CI.md` §3 argues it in full, including why frame
51
+ time is the wrong number (measured flat at 16.70 ms while the main thread went from 1.8 % to 54.5 %
52
+ busy).
51
53
 
52
54
  **`—` is a real, enforced option.** The installer records every off part in `disabled:`, so its
53
55
  rows report `SKIP (opt-out)`; `CL-01` fails if a forbidden part's artifact exists. A repo with
@@ -105,7 +107,7 @@ Then, in order:
105
107
  .goblin/bin/goblin-verify # 37 passed, 1 failed - HP-05, until HANDOFF names a commit
106
108
  gob emit --platform <p> # optional, per platform: the agent skills are an opt-in
107
109
 
108
- A default class-A install (no agent skills) is **green** — `38 passed, 0 failed, 11 advisory,
110
+ A default software-class install (no agent skills) is **green** — `38 passed, 0 failed, 11 advisory,
109
111
  33 skipped`, exit 0 — once
110
112
  `HANDOFF.md` names a commit that exists; before that edit the scaffold's `0000000` placeholder is
111
113
  the one expected red (`37 passed, 1 failed`). Both numbers are measured, not assumed
@@ -164,7 +166,7 @@ The remedy is a reconciliation. The project's file stays the file of record; not
164
166
  .goblin/bin/goblin-verify # HP-02, HP-03, HP-05 go green
165
167
 
166
168
  Success is the class's full green path (`38 passed, 0 failed, 11 advisory, 33 skipped`, exit 0 for
167
- class A) with `git status --short` empty.
169
+ the software class) with `git status --short` empty.
168
170
 
169
171
  The edit is additive and small — measured on the model repo (§1's exemplar, 2450 lines): three
170
172
  headings plus a `State` block, one dated gate line and a `Not verified` block, 15 lines, no line
package/docs/CI.md CHANGED
@@ -43,8 +43,8 @@ not the model.
43
43
 
44
44
  ## 2. What goblin-stack places, and what it checks
45
45
 
46
- The `ci-gate` part is `R` for classes A and F, `O` for C and E, and `-` for B and D
47
- (`manifest/classes.tsv`). When it is installed, the installer renders
46
+ The `ci-gate` part is `R` for **software**, `O` for **game** and **fleet**, and `-` for **service**
47
+ and **research** (`manifest/classes.tsv`). When it is installed, the installer renders
48
48
  `templates/ci/goblin-gate.yml.tmpl` into `.github/workflows/goblin-gate.yml` — one job, no `if:` at
49
49
  any level, whose only step runs `.goblin/bin/goblin-verify`. It is `owned`, so a second install is a
50
50
  no-op and a hand-edited copy is never overwritten.
@@ -70,11 +70,14 @@ comment and cannot count toward a pass.
70
70
 
71
71
  ---
72
72
 
73
- ## 3. The Electron / desktop-shell class (F)
73
+ ## 3. The Electron opt-in (software class)
74
74
 
75
- Class F exists because a desktop shell needs a part no other class has — a **host gate**, a number
76
- measured on a machine with a display — and forbids a thing the others allow: a **renderer that
77
- reaches Node or the filesystem directly**. That is a new row-set, not a flag on class A.
75
+ An Electron app is the **`software`** class with `electron: true`. The opt-in exists because such an
76
+ app needs a part no other shape has — a **host gate**, a number measured on a machine with a display
77
+ — and forbids a thing the plain software class allows: a **renderer that reaches Node or the
78
+ filesystem directly**. The merge measured the old sixth class's part needs identical to `software` on
79
+ all ten parts, so none of this is a sixth column in `manifest/classes.tsv`: it is the `bans:` list and
80
+ the `perf.host_gate:` key, rendered over the software preset by `presets/electron-overlay.yaml`.
78
81
 
79
82
  ### 3.1 The failure surface, and the check for each
80
83
 
@@ -94,14 +97,14 @@ reaches Node or the filesystem directly**. That is a new row-set, not a flag on
94
97
 
95
98
  ### 3.2 The perf lane: one ratchet, and a host gate beside it
96
99
 
97
- There is **one** mechanism, and class F reuses it unchanged: `ratchet: {name, cmd, ceiling}`,
100
+ There is **one** mechanism, and the opt-in reuses it unchanged: `ratchet: {name, cmd, ceiling}`,
98
101
  enforced by `GT-04`/`GT-05` and pinned to a commit by `PF-01`. A second perf mechanism is not
99
102
  introduced.
100
103
 
101
104
  What the ratchet measures here is `app_bundle_bytes` — the packaged bundle's byte count. It is
102
105
  hermetic, it needs no browser, no display and no dependency, and a fat bundle is a slow cold start on
103
106
  every machine. **The FPS number is declared as a host gate instead** (`perf_host_gate:` in
104
- `presets/F-electron.yaml`), and carried in the HANDOFF with the date it was measured.
107
+ `presets/electron-overlay.yaml`), and carried in the HANDOFF with the date it was measured.
105
108
 
106
109
  This is a **deviation from G6 §B.3**, which put `main_thread_busy_pct` in the ratchet, and the reason
107
110
  is measured: the instrument that produces it — CDP `Performance.getMetrics`, or
package/docs/CONTRACTS.md CHANGED
@@ -9,7 +9,10 @@ same way the fleet's own tool reads it. Everything else is line-oriented shell.
9
9
  goblin-install --target <dir> [options]
10
10
 
11
11
  --target <dir> required; the repo root to install into
12
- --class A|B|C|D|E|F required unless --uninstall or --re-pin
12
+ --class <class> required unless --uninstall or --re-pin. One of the five domain classes:
13
+ software · service · game · research · fleet. The letters A-E and the
14
+ older names app (software), agent (fleet) and desktop/F (software + the
15
+ electron opt-in) are read-time aliases.
13
16
  --models <path> model mapping file (default: $GOBLIN_MODELS -> ~/projects/fleet-model.yaml)
14
17
  --practice <path> the referenced standard (default: $GOBLIN_PRACTICE -> ~/projects/PROJECT-PRACTICE.md)
15
18
  --parts <list> comma list to install; default = every part the class requires
@@ -115,7 +118,7 @@ Output is one line per executed row, in manifest order, plus a summary line at t
115
118
  ADV MD-02 code lane and review lane both resolve to the same family
116
119
  SKIP HS-02 no pinned pre-change commit yet - REPLAY not provable
117
120
 
118
- Those four lines are one row of each marking. The summary line of a green default class-A run is:
121
+ Those four lines are one row of each marking. The summary line of a green default software-class run is:
119
122
 
120
123
  38 passed, 0 failed, 11 advisory, 33 skipped
121
124
 
@@ -138,7 +141,7 @@ settings that make a workflow a **gate** are written down.
138
141
 
139
142
  ### A fresh install verifies green
140
143
 
141
- Measured on a fresh DEFAULT class-A install (skills opt-in, W6 neutral-first), committed with no
144
+ Measured on a fresh DEFAULT software-class install (skills opt-in, W6 neutral-first), committed with no
142
145
  hand edit: **`38 passed, 0 failed, 11 advisory, 33 skipped`, exit 0.** Thirty-three rows skip with
143
146
  a reason — the same not-yet rows as before, plus the five skill rows (`SK-01`..`SK-04`,
144
147
  `AU-04`) that skip on the `playbooks` opt-out a skills-free install records: `HS-02` — no pre-change commit
@@ -178,7 +181,7 @@ verifier is reporting FAILs.
178
181
  - **Per part:** `--opt-out <part>` records the part in `disabled:`. `goblin-verify` then reports
179
182
  the part's rows as `SKIP (opt-out)` in the summary, so the opt-out is **visible rather than
180
183
  absent**. The same mechanism is what makes a class's `-` (off) real.
181
- - **The opt-out numbers are pinned (V3-3).** A class-A install with an explicit `--skills no`
184
+ - **The opt-out numbers are pinned (V3-3).** A software-class install with an explicit `--skills no`
182
185
  verifies `38 passed, 0 failed, 11 advisory, 33 skipped`, exit 0, and
183
186
  `tests/t-install-off-switch.sh` asserts that line: a silent drift in the opt-out path is caught
184
187
  rather than left as a number nobody wrote down (the `--skills no` count moved from `37/0/9/11`
@@ -201,7 +204,7 @@ verifier is reporting FAILs.
201
204
 
202
205
  ## The two commands, verbatim
203
206
 
204
- bash bin/goblin-install --target /path/to/repo --class A
207
+ bash bin/goblin-install --target /path/to/repo --class software
205
208
  .goblin/bin/goblin-verify
206
209
 
207
210
  From a checkout, without installing anything:
@@ -164,26 +164,30 @@ OR its `enforced_by` cell does.
164
164
  the installer records every `-` part in `disabled:`, so its rows report `SKIP (opt-out)`
165
165
  instead of silently passing, and `CL-01` fails if a forbidden part's artifact exists.
166
166
 
167
- | part | A | B | C | D | E | F |
168
- |---|---|---|---|---|---|---|
169
- | handoff | R | R | R | R | R | R |
170
- | spec | R | R | R | - | R | R |
171
- | gate | R | R | R | O | R | R |
172
- | replay | R | - | R | - | O | R |
173
- | ratchet | R | O | O | - | O | R |
174
- | pr-gate | O | - | O | - | O | O |
175
- | review-panel | O | - | R | - | O | O |
176
- | playbooks | R | R | R | R | R | R |
177
- | tokens | O | - | - | - | - | O |
178
- | ci-gate | R | - | O | - | O | R |
179
-
180
- Class **F** is the desktop shell: it needs a part no other class has — a **host gate**, a number
181
- measured on a machine with a display — and forbids nothing the others allow except a renderer that
182
- reaches Node directly, which its ban list catches. `ci-gate` is the one part added at W4: when it
183
- is required or optional the installer renders `templates/ci/goblin-gate.yml.tmpl` into
184
- `.github/workflows/goblin-gate.yml`, and when it is `-` the artifact must be **absent** (which is
185
- why `CL-01` keys off that exact path, not the `.github/` directory — a repo is still allowed CI of
186
- its own). `docs/CI.md` is the contract for what that file does and does not make true.
167
+ | part | software | service | game | research | fleet |
168
+ |---|---|---|---|---|---|
169
+ | handoff | R | R | R | R | R |
170
+ | spec | R | R | R | - | R |
171
+ | gate | R | R | R | O | R |
172
+ | replay | R | - | R | - | O |
173
+ | ratchet | R | O | O | - | O |
174
+ | pr-gate | O | - | O | - | O |
175
+ | review-panel | O | - | R | - | O |
176
+ | playbooks | R | R | R | R | R |
177
+ | tokens | O | - | - | - | O |
178
+ | ci-gate | R | - | O | - | O |
179
+
180
+ The five columns carry the domain names; the letters `A`..`E` and the older names `app` (software)
181
+ and `agent` (fleet) are read-time aliases. The old `F` class — the Electron shell — was merged into
182
+ `software`: the merge measured the two need columns identical on all ten parts, so what it added
183
+ lives in config, not in this table. The **electron opt-in** (`electron: true`) turns the electron
184
+ bans `BN-06`..`BN-09` on even when a hand-edited `bans:` list omits them, and requires a declared
185
+ **host gate** — a number measured on a machine with a display; `docs/CI.md` §3 is the contract for
186
+ it. `ci-gate` is the one part added at W4: when it is required or optional the installer renders
187
+ `templates/ci/goblin-gate.yml.tmpl` into `.github/workflows/goblin-gate.yml`, and when it is `-` the
188
+ artifact must be **absent** (which is why `CL-01` keys off that exact path, not the `.github/`
189
+ directory — a repo is still allowed CI of its own). `docs/CI.md` is the contract for what that file
190
+ does and does not make true.
187
191
 
188
192
  ## The ban list (G5)
189
193
 
package/docs/GUIDE.md CHANGED
@@ -3,7 +3,7 @@
3
3
  A step-by-step guide for your first week. **Read this before the README.** The README tells you
4
4
  what the pieces are; this tells you what to *do*, in order, and what you should see when it works.
5
5
 
6
- Version: `0.4.4` · Last measured: 2026-09-25 · Every command and every output below was run on a
6
+ Version: `0.5.0` · Last measured: 2026-10-05 · Every command and every output below was run on a
7
7
  real repository while writing this guide.
8
8
 
9
9
  ---
@@ -97,12 +97,12 @@ see the plan first:
97
97
  git config user.email "you@example.com"
98
98
  git config user.name "you"
99
99
 
100
- gob init --target . --class app --branch main --email "you@example.com" \
100
+ gob init --target . --class software --branch main --email "you@example.com" \
101
101
  --gate "bash tests/run-tests.sh" --yes
102
102
 
103
103
  or the plain installer this wizard drives, if you prefer the one-shot shape:
104
104
 
105
- gob install --target . --class A
105
+ gob install --target . --class software
106
106
 
107
107
  Expected output (this is a real transcript, trimmed):
108
108
 
@@ -199,7 +199,7 @@ Everything you configure lives in **one file**, created once and then never over
199
199
 
200
200
  Open it. The keys that matter on day one:
201
201
 
202
- class: A # A|B|C|D|E|F - what kind of project this is (step 6)
202
+ class: software # software|service|game|research|fleet (A-E are aliases) - what kind of project this is (step 6)
203
203
  branch: main # DECLARED, never assumed
204
204
  owner_email: you@example.com # the commit identity this repo expects
205
205
  practice: /path/to/your-standard.md # optional: your own house rules, hash-pinned
@@ -245,19 +245,21 @@ supplies the default gate shape. Choose by asking *what does "done" mean here?*
245
245
 
246
246
  | Class | Choose it when | "Done" means |
247
247
  |---|---|---|
248
- | **A · Shipped software** | an app, library, or tool users run | a gate set reports measured numbers and a round lands |
249
- | **B · Service / config** | an API, schema, route, or deployment config | the contract is unchanged, or the change is deliberate and migrated |
250
- | **C · Game** | a game | a suite is green **and** a human feel verdict exists |
251
- | **D · Knowledge / research** | notes, a vault, a research directory | a question is answered with sources and is findable |
252
- | **E · Agent-fleet config** | your agent's own config (`~/.hermes`) | the change is applied, verified against the artifact, versioned |
253
- | **F · Desktop shell** | an Electron / desktop app | renderer isolated from Node, main process not busy, no dev dependency shipped |
248
+ | **software** (A) | an app, library, or tool users run | a gate set reports measured numbers and a round lands |
249
+ | **service** (B) | an API, schema, route, or deployment config | the contract is unchanged, or the change is deliberate and migrated |
250
+ | **game** (C) | a game | a suite is green **and** a human feel verdict exists |
251
+ | **research** (D) | notes, a vault, a research directory | a question is answered with sources and is findable |
252
+ | **fleet** (E) | your agent's own config (`~/.hermes`) | the change is applied, verified against the artifact, versioned |
253
+
254
+ An Electron app is **software** with `electron: true` — the opt-in adds the electron bans and a host
255
+ gate, not a sixth class. The old `F` letter still resolves there as an install alias.
254
256
 
255
257
  **Two placements people get wrong:**
256
258
 
257
- - A repo that holds *output* while the code lives elsewhere → **D**, not A. Gating it like an
258
- application gates the wrong artifact.
259
- - A plain input directory that is not a build target → **D** with `--archive`, which tells verify to
260
- expect no HANDOFF and no gates, and to say so.
259
+ - A repo that holds *output* while the code lives elsewhere → **research**, not **software**. Gating
260
+ it like an application gates the wrong artifact.
261
+ - A plain input directory that is not a build target → **research** with `--archive`, which tells
262
+ verify to expect no HANDOFF and no gates, and to say so.
261
263
 
262
264
  Switch class later by editing `class:` in the config and re-running install. The parts you no longer
263
265
  need are recorded as **disabled** and will report `SKIP (opt-out)` rather than failing.
@@ -392,7 +394,7 @@ the next session.
392
394
 
393
395
  ## 9. What to expect on day one (so you do not misread it)
394
396
 
395
- A class-A install lands on a specific shape. The scaffold ships one deliberate red — `HP-05`, the
397
+ A software-class install lands on a specific shape. The scaffold ships one deliberate red — `HP-05`, the
396
398
  `0000000` placeholder in `HANDOFF.md` (§4) — so a literal first run prints:
397
399
 
398
400
  37 passed, 1 failed, 11 advisory, 33 skipped (the one FAIL is HP-05)
@@ -409,7 +411,7 @@ Two readings that are easy to get wrong:
409
411
 
410
412
  - **Advisory rows are not passes.** Ten rules are labelled `advisory` — counted, not enforced, and
411
413
  nine of them carry no executable check at all. The count is capped by `advisory_ceiling: 10`, and
412
- a class-A install already sits at 10 of 10: adding another unenforceable rule fails verify until
414
+ a software-class install already sits at 10 of 10: adding another unenforceable rule fails verify until
413
415
  one is removed. That is intentional. (The summary line can print `11 advisory`: the eleventh ADV
414
416
  line is `JG-02`, a row with a real command of its own that reports ADV here because your model
415
417
  file declares no `judge:` lane — it prints the remedy rather than failing a repo for a fleet's
@@ -485,7 +487,7 @@ engine and silently de-migrate the record).
485
487
 
486
488
  ### Commands
487
489
 
488
- gob install --target <dir> --class A|B|C|D|E|F [options]
490
+ gob install --target <dir> --class <software|service|game|research|fleet> [options]
489
491
  gob install --target <dir> --uninstall
490
492
  gob install --target <dir> --re-pin
491
493
  gob install --target <dir> --upgrade
@@ -581,7 +583,7 @@ with *"prove it was broken first"* — it is the one practice that survives cont
581
583
  # 1. try it somewhere disposable
582
584
  mkdir -p /tmp/gs-try && cd /tmp/gs-try
583
585
  git init -b main
584
- gob install --target . --class A # expect: created 25 (no skills — those are gob emit)
586
+ gob install --target . --class software # expect: created 25 (no skills — those are gob emit)
585
587
 
586
588
  # 2. commit and check
587
589
  git add -A && git commit -m "chore: install gobstack"
@@ -602,7 +604,7 @@ with *"prove it was broken first"* — it is the one practice that survives cont
602
604
 
603
605
  # 5. do it for real, in a repo you care about
604
606
  cd ~/projects/your-project
605
- gob install --target . --class A
607
+ gob install --target . --class software
606
608
  git add -A && git commit -m "chore: adopt gobstack"
607
609
  .goblin/bin/goblin-verify
608
610
  $EDITOR HANDOFF.md # state / gates (dated!) / next / NOT verified
package/docs/LIMITS.md CHANGED
@@ -275,7 +275,8 @@ Electron perf number is a host gate, and the ratchet deliberately carries a diff
275
275
  flow-style `jobs: {…}` mapping is refused, and a `#` inside a quoted string truncates the line
276
276
  it is on. Two further measured gaps: the template's job is `ubuntu-latest` with no cache, so a
277
277
  repo whose gate needs a display, a licence, a GPU or a signed-in session cannot use it at all
278
- (that is a **host** gate — the class-C rule, restated for class F), and a private repo's
278
+ (that is a **host** gate — the class-C rule, restated for class F; **corrected 2026-10-02 (W6):**
279
+ F is merged into `software`, so this is the electron opt-in), and a private repo's
279
280
  Actions minutes are billed to the account (2,000/month free). Measured ground truth at W4:
280
281
  **one** first-party workflow exists in the whole estate and it self-skips; five of the six
281
282
  repos with a remote have none.
@@ -295,7 +296,9 @@ Electron perf number is a host gate, and the ratchet deliberately carries a diff
295
296
  while the main thread went from 1.8 % to 54.5 % busy, `docs/CI.md` §3.3); an absolute FPS
296
297
  claim is not. Three further Electron failure modes are **recorded, not mechanised**, and
297
298
  `docs/CI.md` §4 says why: the dependency-graph boundary check, `ipcMain` sender validation,
298
- and fuses at package time.
299
+ and fuses at package time. **Corrected 2026-10-02 (W6):** this preset is now
300
+ `presets/electron-overlay.yaml`, rendered over `presets/software.yaml` by `--electron` (or the
301
+ `desktop`/`F` install alias) — the old `F` class was merged into `software`.
299
302
  36. **A ban's exemption reaches the probe through its environment, so a custom probe can ignore it.**
300
303
  `bans_exempt:` and the inline `// BAN-OK(<id>): <reason>` are filtered *before* the exit code is
301
304
  chosen, because a filter applied to a probe's stdout afterwards cannot change a verdict — that
@@ -589,3 +592,17 @@ the Node the gates ran under.
589
592
  a source row can never fail a user's repo, and a target row can never substitute for the
590
593
  framework's own suite. Recorded as a definition; `docs/ENFORCEMENT.md`'s scope paragraph
591
594
  carries the same sentence for the reader who arrives there first.
595
+
596
+ 53. **The sixth class is gone: the desktop shell is `software` + `electron: true`, and `A`..`E` are
597
+ read-time aliases.** W6 merged `F` into `software` because their `manifest/classes.tsv` need
598
+ columns were measured identical on all ten parts — the old column added config (the electron
599
+ `bans:`, the `perf.host_gate:`, the `app_bundle_bytes` ratchet, the `dist out release`
600
+ build-output scope), never a part. Measured on the merge tree (2026-10-02): the tsv is 50 rows
601
+ over five classes; `gob init --class desktop`, `--class F` and `--class software --electron`
602
+ render byte-identical `goblin.yaml` (`class: software`, `electron: true`); and a pre-merge repo
603
+ carrying `class: F` with no `electron:` key verifies unchanged, `38 passed, 0 failed, 11
604
+ advisory, 33 skipped`, exit 0, with `git status --porcelain` empty — zero writes, because its
605
+ bans and host gate live in its own config. What this costs, recorded rather than fixed: such a
606
+ repo gets the merge's declaration-time host-gate check only after hand-adding `electron: true`;
607
+ its bans and host gate keep running either way, so nothing fails closed, and the CLI keeps
608
+ accepting `desktop`/`F` as aliases.
@@ -1,61 +1,51 @@
1
1
  class part need
2
- A handoff R
3
- B handoff R
4
- C handoff R
5
- D handoff R
6
- E handoff R
7
- F handoff R
8
- A spec R
9
- B spec R
10
- C spec R
11
- D spec -
12
- E spec R
13
- F spec R
14
- A gate R
15
- B gate R
16
- C gate R
17
- D gate O
18
- E gate R
19
- F gate R
20
- A replay R
21
- B replay -
22
- C replay R
23
- D replay -
24
- E replay O
25
- F replay R
26
- A ratchet R
27
- B ratchet O
28
- C ratchet O
29
- D ratchet -
30
- E ratchet O
31
- F ratchet R
32
- A pr-gate O
33
- B pr-gate -
34
- C pr-gate O
35
- D pr-gate -
36
- E pr-gate O
37
- F pr-gate O
38
- A review-panel O
39
- B review-panel -
40
- C review-panel R
41
- D review-panel -
42
- E review-panel O
43
- F review-panel O
44
- A playbooks R
45
- B playbooks R
46
- C playbooks R
47
- D playbooks R
48
- E playbooks R
49
- F playbooks R
50
- A tokens O
51
- B tokens -
52
- C tokens -
53
- D tokens -
54
- E tokens -
55
- F tokens O
56
- A ci-gate R
57
- B ci-gate -
58
- C ci-gate O
59
- D ci-gate -
60
- E ci-gate O
61
- F ci-gate R
2
+ software handoff R
3
+ service handoff R
4
+ game handoff R
5
+ research handoff R
6
+ fleet handoff R
7
+ software spec R
8
+ service spec R
9
+ game spec R
10
+ research spec -
11
+ fleet spec R
12
+ software gate R
13
+ service gate R
14
+ game gate R
15
+ research gate O
16
+ fleet gate R
17
+ software replay R
18
+ service replay -
19
+ game replay R
20
+ research replay -
21
+ fleet replay O
22
+ software ratchet R
23
+ service ratchet O
24
+ game ratchet O
25
+ research ratchet -
26
+ fleet ratchet O
27
+ software pr-gate O
28
+ service pr-gate -
29
+ game pr-gate O
30
+ research pr-gate -
31
+ fleet pr-gate O
32
+ software review-panel O
33
+ service review-panel -
34
+ game review-panel R
35
+ research review-panel -
36
+ fleet review-panel O
37
+ software playbooks R
38
+ service playbooks R
39
+ game playbooks R
40
+ research playbooks R
41
+ fleet playbooks R
42
+ software tokens O
43
+ service tokens -
44
+ game tokens -
45
+ research tokens -
46
+ fleet tokens O
47
+ software ci-gate R
48
+ service ci-gate -
49
+ game ci-gate O
50
+ research ci-gate -
51
+ fleet ci-gate O
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@techgoblin/gobstack",
3
- "version": "0.4.4-beta.8",
3
+ "version": "0.5.0-beta.1",
4
4
  "description": "Agent-discipline toolkit: one verify command, an enforcement matrix, and LIMITS. bash engine, npm shim.",
5
5
  "bin": {
6
6
  "goblin": "bin/goblin.js",
@@ -0,0 +1,38 @@
1
+ # presets/electron-overlay.yaml — the electron opt-in for the `software` class.
2
+ #
3
+ # The `desktop` class is gone; F was merged into software because their classes.tsv need
4
+ # columns are IDENTICAL on all 10 parts (measured). Everything F added beyond software lives in
5
+ # plain goblin.yaml config keys, so it is an OVERLAY, not a class: `gob install --class software
6
+ # --electron` (or the `--class desktop` / `F` alias, which sets --electron) renders the keys
7
+ # below OVER the software preset. An absent key falls through to presets/software.yaml; a key
8
+ # present here — including an empty one like sec_write_routes: "" — wins.
9
+ #
10
+ # The prose F carried (label / done_means / notes) is preserved here for the record; it is not a
11
+ # rendered template key, so it never reaches a target's goblin.yaml.
12
+ label: Desktop shell
13
+ done_means: the renderer is isolated from Node, the main process is not busy, and the packaged bundle ships no dev dependency
14
+ # software's A-only second gate (the TODO ceiling) is NOT applied: F never had it.
15
+ gate2_name: ""
16
+ gate2_cmd: ""
17
+ # The perf lane is the shipped ratchet, reused verbatim; the hermetic number here is the
18
+ # packaged bundle's byte count (a fat bundle is a slow cold start on every machine).
19
+ ratchet_name: app_bundle_bytes
20
+ ratchet_cmd: find dist out release -type f -exec cat {} + 2>/dev/null | wc -c
21
+ sec_build_output: dist out release
22
+ # A renderer that reaches the filesystem or Node directly is forbidden, so there are no write
23
+ # routes to allow (software allows app/ and src/app/; the overlay blanks that).
24
+ sec_write_routes: ""
25
+ # perf_metric must EQUAL ratchet_name (PF-01 fails when the budget and the measurement disagree).
26
+ perf_metric: app_bundle_bytes
27
+ perf_cmd: find dist out release -type f -exec cat {} + 2>/dev/null | wc -c
28
+ # THE FPS NUMBER IS A HOST GATE, not the ratchet: the instrument that produces
29
+ # main_thread_busy_pct needs Playwright or Electron plus a GUI, which the no-npm contract
30
+ # forbids a shipped rule to launch. It is DECLARED here and carried in the HANDOFF with its date
31
+ # (docs/LIMITS.md #34, docs/CI.md carries the sweep). `electron: true` with this empty FAILs PF-01.
32
+ perf_host_gate: "Electron run - main_thread_busy_pct with a window open (app.getAppMetrics()[i].cpu.percentCPUUsage, or CDP Performance.getMetrics), on a machine with a display"
33
+ # The nine electron bans (G5's mechanism, G6's failure surface): nodeIntegration, context
34
+ # isolation / sandbox, the dangerous webPreferences, and synchronous IPC / @electron/remote. An
35
+ # unlisted ban SKIPs with a reason, so the other classes are unaffected; with electron: true the
36
+ # engine also treats BN-06..09 as enabled even if a hand-edited bans: list omits them.
37
+ bans: BN-01, BN-02, BN-05, BN-06, BN-07, BN-08, BN-09
38
+ notes: a perf number measured on one box is not a user's experience - the renderer half can be a CI job, the Electron half is a host gate and cannot be.
@@ -1,4 +1,4 @@
1
- # presets/E-fleet-config.yaml — class E: agent-fleet configuration.
1
+ # presets/fleet.yaml — the `fleet` class (letters/aliases E, agent).
2
2
  #
3
3
  # "Done" means a config change is applied, verified against the ARTIFACT, and versioned.
4
4
  # The gate therefore checks the artifact (a commit exists, dated), never the intention.
@@ -1,4 +1,4 @@
1
- # presets/C-game.yaml — class C: game.
1
+ # presets/game.yaml — the `game` class (letter/alias C).
2
2
  #
3
3
  # "Done" means a suite green in the Editor AND a human feel verdict. The verdict is a
4
4
  # first-class deliverable, which is why this class requires the review-panel part: the panel
@@ -1,4 +1,4 @@
1
- # presets/D-knowledge.yaml — class D: knowledge / research.
1
+ # presets/research.yaml — the `research` class (letter/alias D).
2
2
  #
3
3
  # "Done" means a question is answered with sources and the answer is findable. A research
4
4
  # note is not a spec, so the SPEC part is OFF, not optional. So are the REPLAY and the PR
@@ -1,4 +1,4 @@
1
- # presets/B-service-config.yaml — class B: service / configuration.
1
+ # presets/service.yaml — the `service` class (letter/alias B).
2
2
  #
3
3
  # "Done" means a contract (schema, route, API) is unchanged, or the change is intentional
4
4
  # and migrated. The class turns off the REPLAY and the PR gate, because a config repo has no
@@ -1,4 +1,4 @@
1
- # presets/A-shipped-software.yaml — class A: shipped software.
1
+ # presets/software.yaml — the `software` class (letters/aliases A, app).
2
2
  #
3
3
  # A class is not a stringency level. It selects WHICH PARTS are required, optional or off
4
4
  # (manifest/classes.tsv), and it supplies the default gate/ratchet shape below. The gate
@@ -7,12 +7,15 @@ description: P8: adopt goblin-stack in a repo - classify, install, verify, then
7
7
 
8
8
  Use when adopting goblin-stack in a repo, or starting one.
9
9
 
10
- 1. **Classify the project A-F.** The class selects which parts are required, optional or off;
11
- it is not a stringency level. F is a desktop shell: it adds the electron bans and a host gate.
10
+ 1. **Classify the project into one of five classes.** `software`, `service`, `game`, `research` or
11
+ `fleet` — the class selects which parts are required, optional or off; it is not a stringency
12
+ level. `software` also carries the electron opt-in (`--electron`): an Electron app is `software`
13
+ with the electron bans and a host gate, not a sixth class. The letters `A`-`E` and the older
14
+ names are read-time aliases.
12
15
  2. **`goblin-install --target <dir> --class <x>`** — the default install is a NEUTRAL harness:
13
16
  no agent skills. Opt in per platform afterwards with `gob emit --platform <p>` (or vendor the
14
17
  Hermes project tier with `--skills yes`).
15
- 3. **`goblin-verify`** — a default class-A install (no agent skills) verifies green:
18
+ 3. **`goblin-verify`** — a default software-class install (no agent skills) verifies green:
16
19
  `38 passed, 0 failed, 11 advisory, 33 skipped`, exit 0, once `HANDOFF.md` names a commit that
17
20
  exists; before that edit the
18
21
  scaffold's `0000000` placeholder is `HP-05`'s one expected day-one red (`37 passed, 1 failed`).
@@ -4,9 +4,17 @@
4
4
  # Format: a flat, line-oriented YAML subset, parsed by .goblin/bin/goblin-lib.sh. No YAML
5
5
  # library, no network, no npm. Keep one key per line and keep comments on their own line.
6
6
 
7
- # Which class this project is (A|B|C|D|E). Selects the required parts (manifest/classes.tsv).
7
+ # Which class this project is: software | service | game | research | fleet.
8
+ # The letters A-E and the older names app/agent/desktop are READ-TIME aliases, so an old
9
+ # value keeps verifying; `desktop`/`F` resolve to software. Selects the required parts
10
+ # (manifest/classes.tsv).
8
11
  class: {{CLASS}}
9
12
 
13
+ # Electron opt-in (software class). true turns on the electron ban set (BN-06..09) even if the
14
+ # bans: list below omits them, and requires a declared perf.host_gate (PF-01 fails without one).
15
+ # The `desktop`/`F` install alias sets this true; a text-editor app leaves it false.
16
+ electron: {{ELECTRON}}
17
+
10
18
  # The default branch, DECLARED, never assumed. A preset that assumes the wrong branch
11
19
  # silently skips a repo.
12
20
  branch: {{BRANCH}}
@@ -1,67 +0,0 @@
1
- # presets/F-electron.yaml — class F: desktop shell (Electron).
2
- #
3
- # A class is not a stringency level: it selects WHICH PARTS are required, optional or off
4
- # (manifest/classes.tsv) and supplies the default gate/ratchet shape. A desktop shell needs a
5
- # part no other class has — a HOST gate, a number measured on a machine with a display — and it
6
- # forbids a thing the others allow: a renderer that reaches the filesystem or Node directly.
7
- # That is a new row-set, not a flag on class A (G6 section B.2, Part B).
8
- #
9
- # THE PERF LANE IS THE SHIPPED RATCHET, NOT A SECOND ONE (G6 section B.3). There is one
10
- # mechanism — `ratchet: {name, cmd, ceiling}`, enforced by GT-04/GT-05 and pinned to a commit by
11
- # PF-01 — and this class reuses it verbatim. What the ratchet measures here is the one hermetic
12
- # perf number a desktop shell has: the packaged bundle's byte count. A fat bundle is a slow
13
- # cold start on every machine, and the number needs no browser, no display and no dependency.
14
- #
15
- # THE FPS NUMBER IS A HOST GATE, and this is a DEVIATION from G6 section B.3, recorded with its
16
- # measured reason. G6 wants `main_thread_busy_pct` in the ratchet. It cannot be: the instrument
17
- # that produces it (CDP `Performance.getMetrics` over a real Chromium, or
18
- # `app.getAppMetrics()[i].cpu.percentCPUUsage` inside a real Electron) needs Playwright or
19
- # Electron plus a GUI, and the dependency contract (docs/CONTRACTS.md) allows a shipped rule
20
- # nothing but bash/git/awk/sed/grep/python3. A `ratchet.cmd` that cannot run on a fresh install
21
- # makes a fresh install BORN RED, which is the one thing every class must not be. So the probe
22
- # belongs to the project, next to the code it measures, and the number it produces is declared
23
- # here as a host gate and carried in the HANDOFF with its date (the class C pattern).
24
- #
25
- # Why frame time is the WRONG number, measured in G6 on a real Chromium (CDP, 0 to 32 ms of
26
- # work per frame): p50 frame time stayed FLAT at 16.70 ms while the main thread went from 1.8%
27
- # to 54.5% busy, and the dropped-frame count was non-monotone (0,0,0,1,3,11,0 — the worst
28
- # workload read 0). A frame-time gate at 16.7 ms is green on the idle tree AND on the loaded
29
- # tree: PROJECT-PRACTICE section 3, reproduced live in the exact metric the note proposed.
30
- # `main_thread_busy_pct` is the only monotone instrument in that sweep (1.8 -> 13.5 -> 26.0 ->
31
- # 54.5 -> 76.9 -> 99.5%), which is why it is named here as the host gate's metric.
32
- # docs/CI.md carries the sweep; docs/LIMITS.md #34 carries the gap.
33
- label: Desktop shell
34
- done_means: the renderer is isolated from Node, the main process is not busy, and the packaged bundle ships no dev dependency
35
- harness_dir: checks
36
- scaffold_checks: yes
37
- gate_name: commit
38
- gate_cmd: git rev-parse --verify --quiet HEAD
39
- ratchet_name: app_bundle_bytes
40
- ratchet_cmd: find dist out release -type f -exec cat {} + 2>/dev/null | wc -c
41
- ratchet_ceiling: measure
42
- replay_env: GOBLIN_PRE_COMMIT
43
- replay_cmd: node checks/{name}.mjs
44
- runtime_data: .goblin/state.json
45
- sec_gitignore_family: yes
46
- sec_build_output: dist out release
47
- sec_audit_cmd: npm audit --json
48
- sec_audit_max_age_days: 90
49
- sec_waiver_max_age_days: 180
50
- sec_write_routes: ""
51
- # perf_metric must EQUAL ratchet_name (PF-01 fails when the budget and the measurement disagree),
52
- # so the hermetic metric is named here too, and the host gate carries the FPS number beside it.
53
- perf_metric: app_bundle_bytes
54
- perf_cmd: find dist out release -type f -exec cat {} + 2>/dev/null | wc -c
55
- perf_baseline_commit: ""
56
- perf_baseline_value: 0
57
- perf_measured: ""
58
- perf_host_gate: "Electron run - main_thread_busy_pct with a window open (app.getAppMetrics()[i].cpu.percentCPUUsage, or CDP Performance.getMetrics), on a machine with a display"
59
- # The nine electron bans (G5's mechanism, G6's failure surface): nodeIntegration, context
60
- # isolation / sandbox, the dangerous webPreferences, and synchronous IPC / @electron/remote.
61
- # An unlisted ban SKIPs with a reason, so the other classes are unaffected by this list.
62
- bans: BN-01, BN-02, BN-05, BN-06, BN-07, BN-08, BN-09
63
- notes: a perf number measured on one box is not a user's experience - the renderer half can be a CI job, the Electron half is a host gate and cannot be.
64
-
65
- # The loop ceiling (G2, LP-03): the most turns ONE loop record may declare in its budget.
66
- # The same number for every class - a ceiling, not a per-class policy. 20 is the engine default.
67
- loop_max_turns_ceiling: 20