@omega.js/client 0.1.0
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/LICENSE +98 -0
- package/README.md +874 -0
- package/dist/index.js +999 -0
- package/dist/modules/analytics.js +584 -0
- package/dist/modules/auth.js +469 -0
- package/dist/modules/bindings.js +319 -0
- package/dist/modules/device.js +282 -0
- package/dist/modules/dom.js +96 -0
- package/dist/modules/features.js +30 -0
- package/dist/modules/firestore.js +313 -0
- package/dist/modules/form-manager.js +1577 -0
- package/dist/modules/icon-core.js +226 -0
- package/dist/modules/icon-renderer.js +149 -0
- package/dist/modules/live-page.js +235 -0
- package/dist/modules/logger.js +36 -0
- package/dist/modules/motion.js +853 -0
- package/dist/modules/notifications.js +433 -0
- package/dist/modules/path-prefix.js +22 -0
- package/dist/modules/request.js +223 -0
- package/dist/modules/sentry.js +108 -0
- package/dist/modules/service-worker.js +237 -0
- package/dist/modules/storage.js +133 -0
- package/dist/modules/triggers.js +117 -0
- package/dist/modules/utilities.js +479 -0
- package/dist/modules/vert-document.js +354 -0
- package/dist/modules/verts.js +1133 -0
- package/dist/vendor/account/engine.js +182 -0
- package/dist/vendor/account/features.js +220 -0
- package/dist/vendor/account/index.js +53 -0
- package/dist/vendor/account/schema.js +272 -0
- package/dist/vendor/account/subscription.js +38 -0
- package/dist/vendor/analytics/adapters/ga4.js +26 -0
- package/dist/vendor/analytics/adapters/meta.js +26 -0
- package/dist/vendor/analytics/adapters/resolve.js +130 -0
- package/dist/vendor/analytics/adapters/tiktok.js +27 -0
- package/dist/vendor/analytics/catalog.js +908 -0
- package/dist/vendor/analytics/consent.js +49 -0
- package/dist/vendor/analytics/core.js +141 -0
- package/dist/vendor/analytics/identity.js +136 -0
- package/dist/vendor/analytics/index.js +170 -0
- package/dist/vendor/analytics/logger.js +40 -0
- package/dist/vendor/analytics/transports/browser.js +110 -0
- package/dist/vendor/monitoring/browser.js +207 -0
- package/dist/vendor/monitoring/core.js +180 -0
- package/dist/vendor/monitoring/logger.js +39 -0
- package/docs/architecture.md +59 -0
- package/docs/bindings.md +235 -0
- package/docs/build-system.md +32 -0
- package/docs/cdp-debugging.md +29 -0
- package/docs/code-patterns.md +96 -0
- package/docs/common-tasks.md +36 -0
- package/docs/dependencies.md +19 -0
- package/docs/index.md +159 -0
- package/docs/modules.md +180 -0
- package/docs/shared/agent-docs.md +89 -0
- package/docs/shared/analytics.md +612 -0
- package/docs/shared/brands.md +51 -0
- package/docs/shared/breaking-changes.md +497 -0
- package/docs/shared/config.md +1387 -0
- package/docs/shared/deploys.md +215 -0
- package/docs/shared/icons.md +201 -0
- package/docs/shared/local-dev.md +147 -0
- package/docs/shared/logging.md +202 -0
- package/docs/shared/monitoring.md +153 -0
- package/docs/shared/publishing.md +183 -0
- package/docs/shared/rulings.md +34 -0
- package/docs/shared/testing.md +147 -0
- package/docs/shared/theming.md +604 -0
- package/docs/shared/translation.md +291 -0
- package/docs/shared/updates.md +61 -0
- package/docs/testing.md +9 -0
- package/package.json +65 -0
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
# Publishing — the runbook
|
|
2
|
+
|
|
3
|
+
> The publish-proving checkpoint's script. Nothing here runs without Ian's explicit GO
|
|
4
|
+
> (standing rule: zero npm publishes / GitHub releases until then). The latch is
|
|
5
|
+
> mechanical: all seven publishables carry `private: true` — npm itself refuses until
|
|
6
|
+
> the unlatch step below.
|
|
7
|
+
|
|
8
|
+
## What publishes, what never does
|
|
9
|
+
|
|
10
|
+
| Publishes (seven) | Never publishes (vendored at prepare, six) |
|
|
11
|
+
|---|---|
|
|
12
|
+
| `@omega.js/backend`, `@omega.js/client`, `@omega.js/desktop`, `@omega.js/extension`, `@omega.js/manager`, `@omega.js/mcp-router`, `@omega.js/web` | `@omega.js/devkit`, `@omega.js/config`, `@omega.js/account`, `@omega.js/template-kit`, `@omega.js/analytics`, `@omega.js/monitoring` |
|
|
13
|
+
|
|
14
|
+
The private six are `VENDORABLE_PACKAGES` in [packages/devkit/tools/vendor.js](../../packages/devkit/tools/vendor.js),
|
|
15
|
+
which is the SSOT: the vendor tool throws on a dist reference to any `@omega.js`
|
|
16
|
+
package not on that list, so read the count from there rather than from this page.
|
|
17
|
+
|
|
18
|
+
`@omega.js/mcp-router` joined the set (Ian 2026-07-30, [#144](https://github.com/Omega-JS-Stack/omega/issues/144)):
|
|
19
|
+
the manager's vendored Claude plugin declares the router, so the router has to be
|
|
20
|
+
installable beside it — a real dependency, never vendored. It ships no docs tree
|
|
21
|
+
(it is not in the vendor lane's `DOCUMENTED_PACKAGES`) and no `exports` map, so the
|
|
22
|
+
plugin's launcher can deep-resolve `@omega.js/mcp-router/bin/mcp-router.js`.
|
|
23
|
+
|
|
24
|
+
Registry-real internal ranges (everything else is workspace `*`): `@omega.js/client`
|
|
25
|
+
`0.1.0` in backend/web/desktop/extension; `@omega.js/backend` and `@omega.js/mcp-router`
|
|
26
|
+
`0.1.0` in manager — EXACT, not carets, because the family is lockstep (below).
|
|
27
|
+
|
|
28
|
+
## Lockstep — the family ships ONE version ([#794](https://github.com/Omega-JS-Stack/omega/issues/794))
|
|
29
|
+
|
|
30
|
+
Changesets carries the seven publishables as a single `fixed` group
|
|
31
|
+
(`.changeset/config.json`; `scripts/changeset-config.test.js` holds that group
|
|
32
|
+
and `release-check.js`'s `PUBLISHABLES` in parity, and release-check itself
|
|
33
|
+
prints a `one version across the family` check). A bump on any one bumps all
|
|
34
|
+
seven to the same number, and packages with no code change republish anyway.
|
|
35
|
+
|
|
36
|
+
Why, in two sentences: ONE number for the family means a brand can never
|
|
37
|
+
install a backend from one release beside a client from another — "everything
|
|
38
|
+
is 0.5.x" is the whole compatibility contract, readable by a human and
|
|
39
|
+
checkable in one comparison. The private internals are VENDORED copies inside
|
|
40
|
+
each framework, so a config-schema change already forces every framework to
|
|
41
|
+
republish; independent numbers only hid that, and let one omega.json5 be
|
|
42
|
+
validated by two validators.
|
|
43
|
+
|
|
44
|
+
`updateInternalDependencies` stays `patch`, so the exact ranges above move
|
|
45
|
+
with the group on every release. In a brand, the same number lands as an exact
|
|
46
|
+
PIN per target ([updates.md](updates.md)) and the manager's boot check refuses
|
|
47
|
+
a brand that ever drifts ([../manager/brand.md](../manager/brand.md)).
|
|
48
|
+
|
|
49
|
+
## What prepare vendors into a tarball
|
|
50
|
+
|
|
51
|
+
Every publishable's prepare `after` hook runs the devkit vendor lane, which
|
|
52
|
+
ships two payloads: the private packages' MODULES into `dist/vendor/`
|
|
53
|
+
(`tools/vendor.js`), and the DOCS into the package root (`tools/vendor-docs.js`
|
|
54
|
+
— the package's guide as `docs/index.md` plus `docs/shared/`, and for
|
|
55
|
+
`@omega.js/manager` also the repo-root map as `docs/AGENTS.md` (links
|
|
56
|
+
retargeted) and `claude-plugin/` + `.claude-plugin/marketplace.json`, the
|
|
57
|
+
plugin a consumer brand enables from its node_modules, `.mcp.json` included —
|
|
58
|
+
its launcher resolves `@omega.js/mcp-router` from the install
|
|
59
|
+
([#144](https://github.com/Omega-JS-Stack/omega/issues/144))). All of it is
|
|
60
|
+
generated and gitignored; `node --test scripts/vendor-docs.test.js` packs the
|
|
61
|
+
six documented packages for real and asserts the tarball listings. A vendor
|
|
62
|
+
failure ABORTS the prepare: every publishable sets `preparePackage.hooks.afterBlocking: true`
|
|
63
|
+
(prepare-package 2.2.0, [#38](https://github.com/Omega-JS-Stack/omega/issues/38)),
|
|
64
|
+
so a tarball can never build missing its vendored internals. Contract:
|
|
65
|
+
[agent-docs.md](agent-docs.md).
|
|
66
|
+
|
|
67
|
+
The MODULE payload is closed over itself ([#739](https://github.com/Omega-JS-Stack/omega/issues/739)): a vendored file's own cross-package requires are rewritten to the sibling vendored copy, and any vendorable only a vendored file needs is vendored too, so the raw-private-reference grep below reads `dist/vendor/` as strictly as the rest of the tree.
|
|
68
|
+
|
|
69
|
+
## The license check ([#320](https://github.com/Omega-JS-Stack/omega/issues/320))
|
|
70
|
+
|
|
71
|
+
A published install needs a LICENSE to enable the payment system and remove the omega
|
|
72
|
+
attribution. A license is a subscription bought on omegajs.dev and the key is that
|
|
73
|
+
account's API key — `OMEGA_LICENSE_KEY` in the brand `.env`, never in omega.json5
|
|
74
|
+
(secret-shape rule). One key per ACCOUNT, unlimited brands for now.
|
|
75
|
+
|
|
76
|
+
**When it runs**: at DEPLOY time, per target, once — `resolveLicenseVerdict({ config, env, transport })`
|
|
77
|
+
in [`@omega.js/devkit/license`](../../packages/devkit/src/license.js). Each target's
|
|
78
|
+
deploy asks and bakes the answer into that artifact; runtime never phones home and the
|
|
79
|
+
payment call stays pure, so a cancelled key holds until the next deploy (accepted — a
|
|
80
|
+
boot or periodic re-check is additive later).
|
|
81
|
+
|
|
82
|
+
| Verdict | When | `payments` | `attribution` |
|
|
83
|
+
|---|---|---|---|
|
|
84
|
+
| licensed | the key resolves an omegajs.dev account whose subscription plan is not the reserved `basic` free sentinel | `live` | `removed` |
|
|
85
|
+
| keyless | no key, an empty key, a `demo-*` project, or a key whose account has no active subscription | `gated` | `shown` |
|
|
86
|
+
|
|
87
|
+
**Loud failure**: a key IS present but the server is unreachable, answers non-2xx, or
|
|
88
|
+
resolves no account → the resolver THROWS. A typo'd or dead key must never quietly ship a
|
|
89
|
+
gated artifact for a brand that is paying.
|
|
90
|
+
|
|
91
|
+
**The keyless-dev carve-out**: a `demo-*` (emulator-only) project short-circuits before
|
|
92
|
+
the network call, key present or not — local dev and the test brands run keyless forever,
|
|
93
|
+
payments in test mode, attribution shown.
|
|
94
|
+
|
|
95
|
+
**The wire**: `GET https://api.omegajs.dev/omega/user?apiKey=<key>&brandId=<brand.id>`.
|
|
96
|
+
The host is a CONSTANT, not config: every other api base in OMEGA derives from a brand's
|
|
97
|
+
own `brand.url` (`api.<host>`) because it belongs to that brand, and this one is the
|
|
98
|
+
PRODUCT's license server — the same host for every brand that installs OMEGA. Server side
|
|
99
|
+
it is the ordinary `GET /user` route resolving an API key (`users` where
|
|
100
|
+
`api.privateKey ==` it), and the plan comes from `@omega.js/account`'s
|
|
101
|
+
`resolveSubscription`, the same derivation the backend and the client run. `brandId` rides
|
|
102
|
+
along and the server ignores it today, so a future per-key brand limit is a server-side
|
|
103
|
+
change alone. `transport` is the injected fetch, so the tests run fully offline.
|
|
104
|
+
|
|
105
|
+
**Delivery**: the env schema declares `OMEGA_LICENSE_KEY` as `ci` for web, desktop and
|
|
106
|
+
extension — their deploys build on Actions runners, so the check runs where the build runs
|
|
107
|
+
— and declares NOTHING for the backend, which deploys straight from the CLI and reads the
|
|
108
|
+
key out of the `.env` cascade in its own process. It never bakes: a baked license key is a
|
|
109
|
+
license key anyone who unpacks the app can copy.
|
|
110
|
+
|
|
111
|
+
**What each target does with the verdict**: the resolver's answer becomes ONE stamp —
|
|
112
|
+
`resolveLicenseStamp({ config, production })` in the same module, which returns
|
|
113
|
+
`{ status: 'licensed'|'keyless', payments, attribution }` and short-circuits to the keyless
|
|
114
|
+
stamp for any build that is not a production one (so a dev build, a watch and a test never
|
|
115
|
+
phone home).
|
|
116
|
+
|
|
117
|
+
| Target | Where the check runs | What the artifact carries | What changes |
|
|
118
|
+
|---|---|---|---|
|
|
119
|
+
| web | `omega build` (the production build — on the runner for a deploy, locally for a local one) | `site.license`, a build fact beside `site.pricing`/`site.brandTokens` | the footer's "Powered by omegajs.dev" block renders only while `site.license.attribution == 'shown'` (themes/base `_includes/frontend/sections/footer.html`) |
|
|
120
|
+
| backend | `omega deploy`, before the stage — the CLI reads the key from the .env cascade in its own process | `OMEGA_LICENSE_STATUS` in the composed `dist/.env` (the one COMPUTED key there; the KEY itself never rides the upload) | `libraries/payment/license.js` refuses Stripe/PayPal/Chargebee `init()` on `keyless`. The `test` provider is never gated, and an ABSENT status — every local lane, the emulator, a test — behaves exactly as before |
|
|
121
|
+
| desktop | the bundle task, production builds only | `OMEGA_BUILD_JSON.license` (outside `config`, the blob the renderer hands @omega.js/client) | nothing at runtime: the artifact records what it was packaged as. Neither target has an attribution surface today, and their payments ride the backend's gate |
|
|
122
|
+
| extension | the bundle task, production builds only (once per build — the one snapshot every browser target then copies) | `OMEGA_BUILD_JSON.license`, baked into every bundle, likewise outside `config` | as desktop |
|
|
123
|
+
|
|
124
|
+
**Honesty system** (spec call 6): plain readable checks, no obfuscation and no artifact
|
|
125
|
+
signing. The legal backing is the Elastic License 2.0 below, whose terms forbid
|
|
126
|
+
circumventing license-key functionality and removing notices.
|
|
127
|
+
|
|
128
|
+
## Pre-flight (any day, no GO needed)
|
|
129
|
+
|
|
130
|
+
1. `npm run release:check` — packs all seven through their real prepare (vendoring
|
|
131
|
+
included), scratch-installs each tarball with local-tarball overrides, resolves,
|
|
132
|
+
and greps the shipped trees for raw private references. **Must be 7/7 green.**
|
|
133
|
+
This is the laptop mirror of CI's pack-smoke.
|
|
134
|
+
2. Full battery green: root `npm test` (packages → corpus → sandbox e2e → journey).
|
|
135
|
+
3. npm auth sanity: `npm whoami` (expected `itwcw2000`). Known parked mystery: `npm org ls omega.js`
|
|
136
|
+
403s on the empty org — the first real publish is the definitive test. If IT 403s,
|
|
137
|
+
the org-owning account must grant publish rights for the `@omega.js` scope.
|
|
138
|
+
4. **License check** ([#349](https://github.com/Omega-JS-Stack/omega/issues/349)): every
|
|
139
|
+
`packages/*/package.json` reads `"license": "Elastic-2.0"`, every publishable carries a
|
|
140
|
+
root `LICENSE` naming the Elastic License 2.0 (npm ships it into the tarball regardless
|
|
141
|
+
of `files`), and no MIT text survives anywhere:
|
|
142
|
+
`grep -rL "Elastic License 2.0" packages/*/LICENSE` must print nothing and
|
|
143
|
+
`grep -ril "MIT License" packages/ --include=LICENSE*` must be empty. A tarball that
|
|
144
|
+
publishes under the wrong license cannot be recalled from the registry, so this runs
|
|
145
|
+
before the unlatch, not after.
|
|
146
|
+
|
|
147
|
+
## Publish day (Ian's GO)
|
|
148
|
+
|
|
149
|
+
1. **Unlatch**: remove `"private": true` from the seven publishables' package.json,
|
|
150
|
+
and ONLY those seven (the six vendorable privates keep theirs forever:
|
|
151
|
+
`VENDORABLE_PACKAGES` in [packages/devkit/tools/vendor.js](../../packages/devkit/tools/vendor.js)
|
|
152
|
+
names them, so the list is never re-typed here).
|
|
153
|
+
2. **Publish** each (changesets is configured lockstep + `access: public`, so the
|
|
154
|
+
seven go out at ONE number; for the FIRST 0.1.0 the direct form per package is
|
|
155
|
+
equally fine):
|
|
156
|
+
`npm publish --workspace=packages/<name>` — order matters only where a dependent
|
|
157
|
+
waits on a dependency: **client and backend before their dependents**
|
|
158
|
+
(web/desktop/extension need client on the registry; manager needs backend and
|
|
159
|
+
mcp-router). Safe order: client → backend → mcp-router → extension → desktop →
|
|
160
|
+
web → manager. 2FA/OTP prompts surface here on first publish.
|
|
161
|
+
3. **Verify from the outside**: in an empty temp dir, `npm install @omega.js/web`
|
|
162
|
+
(and one more, e.g. manager) — install + `require.resolve` must succeed with no
|
|
163
|
+
overrides. That is the moment the untested-lane risk is retired.
|
|
164
|
+
4. **Flip omega-brand to registry specs**: from the brand root,
|
|
165
|
+
`npx omega i live` — tree-wide `file:` → the EXACT `0.1.0` pin + one registry
|
|
166
|
+
install (`restoreRegistrySpecs` writes the linked copy's version with no
|
|
167
|
+
caret, because the family is lockstep; `omega i local` is the way back for
|
|
168
|
+
local-era work). Commit the brand's manifest+lock change.
|
|
169
|
+
5. **Brand proof**: brand `npm run manage` (manage cycle) + a website build — the brand
|
|
170
|
+
now runs on registry packages; CI-dispatch web deploys become buildable (the
|
|
171
|
+
deploy guard stops refusing once no `file:` specs remain).
|
|
172
|
+
6. Record: CHANGELOG entry + close the tracking issue; re-latch nothing — published is the
|
|
173
|
+
new normal, versions move by changesets from here.
|
|
174
|
+
|
|
175
|
+
## After the first publish
|
|
176
|
+
|
|
177
|
+
- 0.x caret ranges float patch-only (npm's conservative 0.x behavior) — breaking
|
|
178
|
+
changes bump minor and consumers move deliberately. A BRAND floats nothing: the
|
|
179
|
+
manager pins every target exactly, so `omega update` is the one thing that moves
|
|
180
|
+
a brand, and it moves the whole family ([updates.md](updates.md)).
|
|
181
|
+
- 1.0.0 is a later, deliberate graduation (Ian's call), not an accumulation.
|
|
182
|
+
- The publish is also the brand-CI-build unlock: no tarball vendoring exists by
|
|
183
|
+
design — the registry is the lane CI installs from.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Standing rulings
|
|
2
|
+
|
|
3
|
+
Ian's durable rulings, migrated verbatim from PROGRESS.md's Rulings lane when the board retired (v4 migration, 2026-07-27). These bind all work in this monorepo; new rulings land here (or in AGENTS.md when they are architecture). Per-item decisions live on their issues.
|
|
4
|
+
|
|
5
|
+
- Ian 2026-09-09: same-name rule for global + specific values: wherever a site-wide default and a specific override describe the same thing (config default vs page frontmatter, brand vs target, company vs brand, shared vs instance), they share ONE name and ONE shape at every level, the specific level overrides, and a differently named pair is a defect (`seo.index` vs `meta.index` was the case; #564 folds it into `meta.index`). Applies to every config item, never only page-vs-site.
|
|
6
|
+
- Ian 2026-07-21: adblock-safe naming — the ad system speaks vert EVERYWHERE (paths, DOM, collection, API, modules); only ads.txt, Google's own ad* tokens, `advertising` config key say ad
|
|
7
|
+
- Ian 2026-07-21: "DO NOT USE EM DASHES… REWRITE EVERYTHING TO MAKE SENSE WITHUT IT" — site/brand copy never uses em dashes; manual rewrites, not deletions. Operating home with scope + exemptions: docs/shared/theming.md § Copy register (2026-08-15)
|
|
8
|
+
- Ian 2026-07-10: continuous mode — iterate/build/test autonomously, checkpoint after checkpoint; stop only for serious errors or genuinely-Ian decisions
|
|
9
|
+
- Ian 2026-07-19: "I refuse to run a single command — wrap it in npm start, self healing idempotent" — absorb, never hand back; blocked one-offs = framework gaps; wrapped verbs only
|
|
10
|
+
- Ian 2026-07-20: mirrored-implementation rule — same feature, same shape, every framework (cp242 deploys enforced it)
|
|
11
|
+
- Ian 2026-07-20: local-omega-in-production is a SUPPORTED feature — deploys auto-detect linked local frameworks and take local-artifact lanes
|
|
12
|
+
- Ian 2026-07-18: "still use local … until we are fully locked on all decisions that may result in breaking changes" — the local era (file: specs) holds until then
|
|
13
|
+
- Ian 2026-07-19: 0.x until live publishes are proven; 1.0.0 is a later deliberate graduation; zero npm publishes + zero GH releases until GO (old names ship from legacy repos)
|
|
14
|
+
- Ian 2026-07-12: website target = GH Pages ALWAYS; Firebase hosting is the backend/api surface only; GH Pages DNS defaults correct as-is
|
|
15
|
+
- Ian 2026-07-12: FA Pro = local folder route via OMEGA_FONTAWESOME_ROOT (no npm token); skins design within solid/regular/brands
|
|
16
|
+
- Ian 2026-07-11/12: playground = live test infra (Blaze/break/delete); payment+adjacent gated; deploys sparing + named; other brands deploy ONLY on explicit ask
|
|
17
|
+
- Ian 2026-07-13/14: ad-hoc writes to real ITW resources stay gated — manage services' own convergence paths are the sanctioned route (2b mints included)
|
|
18
|
+
- Ian 2026-07-18: credential copies from existing ITW brands SANCTIONED (brand .envs + omega-manager/.brands); new brands mint fresh identity keys; classifier-blocked copies fall to Ian
|
|
19
|
+
- Ian 2026-07-12 (final): NO ITW CLI login — cached browser-OAuth + normal CLI login cover all; never suggest firebase/gcloud login as ITW; npm start = the blessed form *(command superseded 2026-08-13 by [#227](https://github.com/Omega-JS-Stack/omega/issues/227): the blessed reconcile form is now `npm run manage`; `npm start` boots the dev stack)*
|
|
20
|
+
- Ian 2026-07-10: data-shape preservation — Firestore shapes + route semantics presumed good; breaking changes needing migration = flag with plan, don't build
|
|
21
|
+
- Ian 2026-07-06: no backwards compat (dual-read cancelled) — new way only
|
|
22
|
+
- Ian 2026-07-09: legacy repos READ-ONLY (omega-manager, all framework + consumer repos); migrators/verifiers/B5 verify/audit port PINNED; MAM parked
|
|
23
|
+
- Ian 2026-07-20: per-target docs retire in brand context — the brand root is the ONE home (AGENTS.md chain + one README/docs/CHANGELOG)
|
|
24
|
+
- Standing: secrets never in omega.json5 (.env only; config hard-fails); npu never raw npm/npx; explicit `git -C`; commit-and-continue; de-ITW to config = standard scope
|
|
25
|
+
- Standing: checkpoint discipline — survey → design → implement → tests → sandbox/fixture proof → docs → commit; live checks never touch real ITW resources outside sanctioned paths
|
|
26
|
+
- Ian 2026-07-30: uniformity — commands/surfaces of the same TYPE act the SAME; no split defaults within one family (the CLI read/write emulator split was the offense: every backend CLI subcommand now defaults to the emulator, `--production` the only path to live)
|
|
27
|
+
- Ian 2026-07-30: NO legacy accommodations in the new system — no code path accepting a superseded form; breaking changes get DOCUMENTED (register: #148) and migrated once, manually (playbook: #149); the config-convert input lane is the one sanctioned legacy-reading exception
|
|
28
|
+
- Ian 2026-07-30: company membership is the `.omega/company.json` stamp POINTER — brands never physically nest inside a company folder; anything resolving the company must follow the stamp, never the directory tree
|
|
29
|
+
- Ian 2026-08-06: harmonize at BUILD time, never in a later pass — when a mechanism lands in one framework, its shared home (devkit) and the mirroring evaluation happen in the same work item; "wait for the harmonization pass" is not an accepted answer (first application: the #200 captured-read helper lifted to devkit pre-ship)
|
|
30
|
+
- Ian 2026-08-20: migrations converge by SHAPE, not by version steps — each fix detects its legacy pattern in the doc itself, converged docs are proven no-ops, still-invalid docs surface loudly in the audit; every future doc reshape adds its convergent fix to the migrations pipeline in the SAME work item (register: docs/shared/breaking-changes.md)
|
|
31
|
+
- Ian 2026-08-20: writing real data is OPT-IN for one-off scripts/processes — any standalone script that mutates live data (Firestore docs, mailing lists, provider accounts) previews by default and writes only under an explicit `--execute`; the manage/reconciliation services (own `--dry-run` + convergence) and scripts that only write tracked files (git diff is the review) are out of scope; template: the migrations service (#394)
|
|
32
|
+
|
|
33
|
+
- Ian 2026-09-03: `--target=<a,b>` is the ONE target picker on every brand-root fan-out verb (test, deploy, build, clean, dev, update); `--only`/`--except` are retired, not aliased — `--only` collided with Firebase's own `firebase deploy --only hosting` (register: https://github.com/Omega-JS-Stack/omega/issues/780)
|
|
34
|
+
- Ian 2026-09-03: an ORPHANED process of the emulator family (parent gone, this user's uid, a strict command-shape match) is NOBODY's and every emulator boot reaps it, whatever project it names, with no config and no warning-only mode. The family's shapes are named once, where the reap is described: [backend/index.md](../backend/index.md). Supersedes the #293 line that another brand's orphans stay; the #293 lesson survives as the strict family match replacing the loose name regex (register: https://github.com/Omega-JS-Stack/omega/issues/781)
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
# Testing
|
|
2
|
+
|
|
3
|
+
## The layered mantra (ratified 2026-07-28, #46)
|
|
4
|
+
|
|
5
|
+
**Every behavior is proven at the LOWEST layer that can prove it, and the shape is MIRRORED across all frameworks.** This is standing doctrine — it governs every future test, and existing tests that violate it get healed, not grandfathered.
|
|
6
|
+
|
|
7
|
+
1. **Unit** — a single function or module, in isolation, inside its own package. If a unit test can prove it, nothing above may be the only proof.
|
|
8
|
+
2. **Integration** — a whole system inside ONE package, real wiring, no mocks of the package's own code (the js_patterns rule: never mock what you can test real).
|
|
9
|
+
3. **End-to-end** — reserved for contracts that CROSS framework boundaries (client↔backend, desktop↔backend, brand↔brand, consumer↔monorepo). An e2e lane that only exercises one package's internals is mis-layered: push the assertion down.
|
|
10
|
+
|
|
11
|
+
Mirrored means: the same lanes, the same runner semantics, the same `test/` layout and naming, the same scope grammar (below) in every package. A test that exists in one framework's suite exists in its siblings' suites wherever the surface exists (the mirrored-implementation rule applied to test shape).
|
|
12
|
+
|
|
13
|
+
### Mirrored suite shape
|
|
14
|
+
|
|
15
|
+
Every package's suite wears the same clothes:
|
|
16
|
+
|
|
17
|
+
- **Top-level `test/`** — one directory at the package root, mirroring the source tree beneath it. No `test/tests/`, no per-source-dir `__tests__`. Deliberate exception (#46 disposition): desktop and extension keep their suites at `src/test/` — the layer-tagged harness (runners, fixture apps, per-layer suites) is framework source their own `omega test` runner resolves from, and relocating it buys no mirror benefit for real churn risk. The five plain-node email test files that used to sit colocated under backend's `src/manager/libraries/email/` — run by hand, outside every lane — were healed under `packages/backend/test/email/` in their mirrored spots ([#105](https://github.com/Omega-JS-Stack/omega/issues/105)); the runner discovers them like everything else.
|
|
18
|
+
- **`*.test.js` filenames** — the suffix IS the discovery signal, everywhere. Backend's suite now matches (its runner discovers `.test.js` only — no bare-`.js` fallback); `_`-prefixed files and directories stay excluded at any depth for shared helpers and fixtures.
|
|
19
|
+
- **`node --test`** is the default runner (`node --test test/*.test.js` in the npm script), unless the package's own runner is the motivated path: backend, desktop, and extension self-test through `omega test` because their suites need a booted emulator/app and the C5 scope grammar, and devkit wraps the same runner in `scripts/run-tests.js` for its two-pass shape: a `node --test` main pass over every other file, then `e2e-harness.test.js` on its own, executed DIRECTLY (no `--test`, so no runner child and no result-stream IPC to corrupt, the mechanism behind that suite's long-running flake, [#36](https://github.com/Omega-JS-Stack/omega/issues/36)), with one retry that banks the failing output to `.temp/`.
|
|
20
|
+
- **Every case file exports through `defineCases()`** — `module.exports = defineCases({ tests: [...] })`, from `@omega.js/devkit/test/define-cases` ([#630](https://github.com/Omega-JS-Stack/omega/issues/630)). `node --test` on a case file only LOADS it, so the cases never run and the report reads `pass 1`: a hollow green. The wrapper turns that load into a loud throw naming `npx omega test <path>`, and stays a pass-through for the real lane (the runners call `markRunnerActive()` first). A devkit guard fails any unwrapped case file, so a new one cannot regress.
|
|
21
|
+
|
|
22
|
+
The always-wrong shapes — a `__tests__/` directory, a `*.spec.*` filename, `test/tests/` nesting — are bounced at write time by the omega plugin's shape hook (`agent-plugins/claude/hooks/shape/`), in this repo and in every consumer session the plugin loads in. The layer CHOICE stays judgment; the hook guards only the mechanical shape.
|
|
23
|
+
|
|
24
|
+
Layer names inside a suite are platform-native on purpose: desktop's `main`/`renderer` and extension's `background`/`view` name the runtimes those platforms actually have. That is vocabulary, not drift — the mirroring rule is about layout, naming, and runner semantics, not about pretending every platform has the same layers.
|
|
25
|
+
|
|
26
|
+
## The three verification tiers (what runs when)
|
|
27
|
+
|
|
28
|
+
| Tier | What | Command | When |
|
|
29
|
+
|------|------|---------|------|
|
|
30
|
+
| 1 — Package suites | Each package's own `node --test` (config, devkit, manager, web, client, backend boot, …) plus the root `scripts/*.test.js` guards (secrets-copier etc.) | `npm test` in the package, or `npm run test:packages` at the root — **`test:packages` is also the QUICK lane** (no corpus/e2e/journey) | Every checkpoint |
|
|
31
|
+
| 2 — Corpus (automated consumers) | The **brand-shape corpus** (7+ generated brand shapes: real onboard + real Eleventy builds with per-shape invariants — themes, target combos, content, font preloads; offline, no installs) then the sandbox **backend corpus** (framework routes/events/rules through a REAL consumer + real emulator) | `npm run test:corpus` at the root | Every checkpoint that touches runtime behavior |
|
|
32
|
+
| 2a — Sandbox e2e | The **cross-stack e2e** (headless Chromium → website → backend: signup/signin/subscribe/cancel/refund/data-request/delete lifecycle, plus the four chart types drawn as real SVG marks). It runs the consumer-brand lane file below directly (the root lane calls `node test/e2e/run.js`, not the brand-root `omega test` walk): `brands/sandbox-brand/test/e2e/run.js` on the devkit harness, driving a REAL `@omega.js/web` page served by the REAL `omega dev` against the emulator with its seeded personas, every port allocator-bumped ([#775](https://github.com/Omega-JS-Stack/omega/issues/775)) | `npm run test:e2e` at the root | Every checkpoint; `OMEGA_SKIP_E2E=1` to skip |
|
|
33
|
+
| 2b — Verts company-mode e2e | The **cross-brand verts (house-ads) proof** (OMEGA Playground's backend emulator serves seeded `verts` inventory — HTML unit / scoring / 204 / fail-closed redirect / whitelist scoping — and The Daily Build consumes as `source: 'company'`: real `@omega.js/config` compose → real `@omega.js/client` resolution → the consumer-built URL fetched against the parent stack) | `npm run test:verts` at the root | Every checkpoint touching the verts chain; `OMEGA_SKIP_E2E=1` to skip |
|
|
34
|
+
| 2c — Extension auth e2e | The **extension ↔ backend auth boundary in a REAL Chrome** ([scripts/e2e-extension-auth.js](../../scripts/e2e-extension-auth.js)): the playground's extension app builds as a TESTING build, headless Chrome loads it unpacked, the brand-site `?authToken=` tab drives the background SW's real sign-in, and the popup context's `omega:syncAuth` message makes the SW fetch a fresh custom token from the emulator — the uid round trip asserted INSIDE the extension. Offline by construction (host-resolver rules NXDOMAIN everything but the local stack). It FOLLOWS a bumped port like every other lane ([#744](https://github.com/Omega-JS-Stack/omega/issues/744)): the emulator boots FIRST and the extension builds second, so the bundle task bakes the live ports into `OMEGA_BUILD_JSON`'s `config.dev.ports` — the only channel a browser context has — and the lane reads the bake back out of the packaged service worker's own bundle and asserts the baked hosting/auth pair equals the resolved map before Chrome ever starts | `npm run test:e2e-extension` at the root | Every checkpoint touching extension auth, the SW build config, or the `/user/token` wire; `OMEGA_SKIP_E2E=1` to skip, and no puppeteer Chrome → SKIPPED (exit 0, reason printed) |
|
|
35
|
+
| 2d — Desktop auth e2e | The **desktop ↔ backend auth boundary in a REAL Electron app** ([scripts/e2e-desktop-auth.js](../../scripts/e2e-desktop-auth.js)): a consumer app staged from @omega.js/desktop's bundled fixture is built and booted by the real boot runner, then a SECOND Electron instance launches carrying `<brand.id>://auth/token?authToken=…` — the OS single-instance lock forwards that argv to the running app, whose real `second-instance` → deep-link → client-bridge chain signs it in. Both sides of the process boundary are asserted: main's client-bridge on the emulator user, and the renderer's own @omega.js/client Firebase on the same uid (it learned only through the real `desktop:auth:sign-in-with-token` broadcast). Offline by construction — a testing run connects main's auth to the emulator and the staged config points the renderer's client at the same ports | `npm run test:e2e-desktop` at the root | Every checkpoint touching desktop auth, the deep-link routes, or the `/user/token` wire; `OMEGA_SKIP_E2E=1` to skip, and no electron binary (or no built @omega.js/desktop `dist/`) → SKIPPED (exit 0, reason printed) |
|
|
36
|
+
| 2e — User flows e2e (real browser) | The **CRUCIAL user flows through the actual UI** ([scripts/e2e-flows.js](../../scripts/e2e-flows.js)): headless Chromium against the playground's full emulator suite (seeded personas) plus the REAL `omega dev` — the auth-emulator proxy the provider redirect leg needs lives only there. Five areas — four per spec item of [#155](https://github.com/Omega-JS-Stack/omega/issues/155), plus the billing journeys of [#209](https://github.com/Omega-JS-Stack/omega/issues/209): **auth** (the Google picker's real redirect leg on /signup and again with `authReturnUrl`, `?authSignout=true` + the account page's kick-out, the empty-return loud failure, and the password FORMS — /signin, /signup, /reset), **checkout** (bound state, armed payment buttons, a `_dev_cardProvider=test` payment landing on /payment/confirmation with its order), **verts** (an unfilled slot laddering no-fill → promo, the card content-sized under its slot ceiling, the UTM set on the promo link and the click forwarded out of the frame), **account** (a signed-in policy page rendering the persona's account state from the emulator), **billing journeys** (one paid lifecycle per DEDICATED seeded persona — upgrade, cancel, a declined renewal, a trial converting — each verdict read off the RENDERED account page; the two end states no UI can reach post a hand-built test webhook at the backend exactly as a provider would). **Hard precondition**: `OMEGA_WEBHOOK_KEY` must be in the playground backend's `.env` cascade — the webhook route authenticates on it, and the lane throws `OMEGA_WEBHOOK_KEY is missing from the playground backend's .env cascade` at startup rather than running a crippled pass. Owns its stack: it HOLDS every classic port so both children bump onto fresh ones, so it never disturbs a live dev boot | `npm run test:flows` at the root | Every checkpoint touching auth pages, checkout, the vert ladder, the account page, or the billing lifecycle; `OMEGA_SKIP_E2E=1` to skip, and no puppeteer Chrome → SKIPPED (exit 0, reason printed) |
|
|
37
|
+
| 2.5 — Wizard journey (outside-monorepo consumer) | The FULL consumer story in a temp brand born OUTSIDE the monorepo: real onboard wizard (flags) → `i local` tree link → `omega dev` boot + branded-homepage probe → headless creds-scrubbed manage (update must build every target) | `npm run test:journey` at the root (also the tail of root `npm test`) | The full sequence, and any change to onboard/linking/boot plumbing |
|
|
38
|
+
| 3 — Playground (live rehearsal) | The 24-service manage pipeline against REAL cloud (Firebase, Cloudflare, SendGrid, …) | `npm run pipeline` in `brands/omega-playground` | SPARINGLY — Ian-authorized (real infra, real cost) |
|
|
39
|
+
| C — Consumer brand e2e | Every OMEGA brand's OWN browser lane, `<brandRoot>/test/e2e/run.js` on [@omega.js/devkit/test/e2e-harness](../../packages/devkit/src/test/e2e-harness.js): the brand's real pages against its real local stack (the backend emulator with its seeded personas plus the real `omega dev`), on allocator ports beside a live dev boot. The brand-root walk, the `--target` picker and the fanned-out flags: [../manager/brand.md](../manager/brand.md) ([#775](https://github.com/Omega-JS-Stack/omega/issues/775)) | `npx omega test` at the brand root, after every target | Every brand checkpoint. Tier 2a IS this lane, run by the sandbox brand |
|
|
40
|
+
|
|
41
|
+
**Opt-in lanes are not a tier and never join a run by accident.** A lane is a set of suites that exist only for the real external thing — credentials, the network, a vendor CLI — plus a GATE deciding whether they may run at all. It is reached exactly one way, `npx omega test --lane=<name>`, and a gate that cannot be satisfied prints ONE skip line and exits clean; the lane's own `test/<lane>/` directory is excluded from discovery unless its gate opened it, so no default run, no CI tier and no path filter can pull it in. Today there is one: backend's `--lane=stripe-live`, which forwards REAL Stripe test-mode webhooks into the local emulator and refuses to open on anything that is not an `sk_test_` secret ([packages/backend/docs/test-framework.md](../../packages/backend/docs/test-framework.md#opt-in-lanes---lane)). Distinct from `--extended`, which is a *mode* letting the same suites make real calls.
|
|
42
|
+
|
|
43
|
+
**Root `npm test` runs tiers 1 + 2 + 2a + 2b + 2c + 2d + 2e + 2.5 in one shot** (package suites → corpus → sandbox e2e → extension auth e2e → desktop auth e2e → verts company-mode e2e → auth-token e2e → user flows e2e → wizard journey, sequentially — emulator runs must never overlap). Tier 1 expands `packages/*` only — the brands/* workspaces are covered by their own dedicated stages (the sandbox e2e used to run TWICE per root test through both lanes). The sandbox is offline-only (fake `demo-*` project); the journey brand is `demo-*`/`.invalid`-scoped and creds-scrubbed; the playground is the only tier that touches real cloud.
|
|
44
|
+
|
|
45
|
+
**One seeded persona, one testable purpose** (Ian 2026-08-19, [#369](https://github.com/Omega-JS-Stack/omega/issues/369)). Every persona the backend seeds ([packages/backend/src/test/test-accounts.js](../../packages/backend/src/test/test-accounts.js)) is a FULL realistic account — a real profile, a real signup context, the devices an established user is signed in on ([#327](https://github.com/Omega-JS-Stack/omega/issues/327)) — carrying exactly ONE distinguishing state on top, and a lane reads that state off the persona built to demonstrate it. New state gets a NEW persona, never a rider on an existing one: referrals live on the `referrer`/`referred` pair alone and were stripped back off the steady-state subscriber QA signs in as to check billing ([#363](https://github.com/Omega-JS-Stack/omega/issues/363)). A persona telling two stories makes a failure ambiguous about which one broke, and it quietly answers the assertion the other lane came to make.
|
|
46
|
+
|
|
47
|
+
**And every account a test uses is DECLARED there, never minted mid-test** (Ian 2026-08-20, [#406](https://github.com/Omega-JS-Stack/omega/issues/406)). The isolation doctrine covers all suites, not just payments: an account is exclusive to the one suite that drives it, so suites run in any order with no state pollution, and the healthy ones live in the roster as machinery personas (no `palette` label — machinery is never offered to a human in the dev switcher) named for the suite that drives them. A suite still writes the STATE its scenario starts from; what it no longer does is stand the account up. The one exception is a deliberately BROKEN state — an auth user with no doc, a doc with no auth user — which the seed cannot make by construction, so the guard suites that need half a pair build it themselves, in the suite, with `admin.auth().createUser` ([packages/backend/test/routes/payments/intent-purchaser-guard.test.js](../../packages/backend/test/routes/payments/intent-purchaser-guard.test.js)).
|
|
48
|
+
|
|
49
|
+
**Every tier-1 suite preloads the stdout guard** ([#356](https://github.com/Omega-JS-Stack/omega/issues/356)). `node --test` reads a child file's results as v8 frames on that child's STDOUT, and console bytes on the same fd desync node 24's parser — a multibyte character behind a frame makes its SIGNED length negative and the whole FILE dies with `Unable to deserialize cloned data` ([#321](https://github.com/Omega-JS-Stack/omega/issues/321)). So every `node --test` command in this repo carries `--require @omega.js/devkit/test/stdout-guard`, which the runner forwards to each child: string writes go to stderr (still in the report), the protocol pipe carries frames alone, and a spawned child's inherited stdout is remapped to fd 2. Wiring a new suite means adding that flag — `scripts/stdout-guard-wiring.test.js` fails the lane otherwise. The frameworks whose `omega test` runs the in-process runner (backend, desktop, extension) have no frame pipe and stay unwired. Retires when the repo's pinned node reaches >=26.7.0, which reads the length unsigned.
|
|
50
|
+
|
|
51
|
+
**Port isolation cuts two ways.** Every lane FOLLOWS a bumped port — including the extension lane, which builds after the boot so the resolved map is baked into the artifact the SW reads ([#744](https://github.com/Omega-JS-Stack/omega/issues/744)); the user-flows lane REFUSES the classics outright — it binds every classic port on all three addresses (127.0.0.1, ::1, wildcard: Node's SO_REUSEADDR means one bind is not enough) for the whole run, so the emulator CLI and `omega dev` both bump onto fresh ports and a developer's live stack is untouched. A classic port that is already busy is somebody else's: the hold simply fails and the allocator bumps around it as always. The website port is allocated by the LANE and handed to both children as `OMEGA_WEBSITE_PORT`, because the backend builds checkout confirmation URLs from it and boots first.
|
|
52
|
+
|
|
53
|
+
**The auth lanes divide by SURFACE.** `npm run test:auth` ([scripts/e2e-auth-token.js](../../scripts/e2e-auth-token.js)) is the fast WIRE-CONTRACT lane: the desktop client-bridge required in-process from node against the emulator, proving the custom-token round trip in seconds. Tiers 2c and 2d are the REAL-SURFACE proofs of the same chain — the extension's background SW inside actual Chrome, and the desktop app inside actual Electron with a real OS-delivered deep link. A change to the token wire runs the fast lane; a change to how either app RECEIVES it runs its real-surface lane.
|
|
54
|
+
|
|
55
|
+
**Every lane leaves a log.** A lane tees its full output — its own lines plus every child command's — to `.temp/logs/<lane>.log` (`test:packages` → `.temp/logs/test-packages.log`), and each e2e runner writes per-step verdicts to `.temp/<lane>/steps.log` beside its environment logs, so `grep '^FAIL' .temp/*/steps.log` names the failing step even after a lane was killed. Truncated per run; read them instead of re-running a lane to see what it said ([logging.md](logging.md)).
|
|
56
|
+
|
|
57
|
+
**A lane is the LOADED context, so the watch deadlines scale with it** ([#211](https://github.com/Omega-JS-Stack/omega/issues/211)). Web's rebuild-watching suites (`dev-watch`, `live-decisions`, `dev-server-restart`) poll a built page until the watcher rebuilds it; the 30s deadline that makes a solo run fail FAST is the one that blows under the full parallel suite — load, not breakage. They read their deadline from `test/lib/deadlines.js`, which multiplies the 30s base by `OMEGA_TEST_DEADLINE_SCALE`; `scripts/lane.js` exports `3` to every child (an outer lane's or the caller's value passes through untouched), so a lane gets 90s and a solo `node --test <file>` — which never goes through a lane — keeps the tight base. A junk multiplier throws rather than silently running unscaled.
|
|
58
|
+
|
|
59
|
+
**The deadline was never the whole answer, so those suites got their own serial lane** ([#344](https://github.com/Omega-JS-Stack/omega/issues/344)). Nine lane-only sightings later, instrumented captures showed the lane failures are two distinct things, neither of them a slow rebuild:
|
|
60
|
+
|
|
61
|
+
- **A doubled config reset** (`3 !== 2`, with Eleventy's "You called Benchmark after() without a before()" alongside it). One save reaches chokidar once per registered watch-target path FORM, and web registers two for a cwd-contained dir (deliberately — see `registerTemplateWatchTargets`). Normally the two events land in one throttle window; under load they straddle the build, and the second walks past Eleventy's `watchManager.isBuildRunning()` guard — a config reset REPLACES `watchManager` part way through the build it is serving — into a build concurrent with the one in flight. Unfixable from our side without dropping a form, which was tried and measured worse (0/8 red vs 5/8), so the suite is event-keyed instead: it asserts the edit rode the reset lane, never how many resets it took.
|
|
62
|
+
- **A starved watcher.** The packaged-layer watcher received zero chokidar events — not one raw event — for 91 seconds, while `getWatched()` still listed the edited file. It correlates with load, so the suites now run away from it.
|
|
63
|
+
|
|
64
|
+
**The knob only reaches a run the LANE started, so the deadline reads the MACHINE too** ([#615](https://github.com/Omega-JS-Stack/omega/issues/615)). Three more sightings on 2026-08-25 — two workers and a backstop run — were bare `npm test -w packages/web` runs beside other work, which never come through `scripts/lane.js` and so ran at the flat 30s. `rebuildDeadlineMs()` derives it now: **the 30s floor × measured contention (the 1-minute load average per CPU, floored at 1 and capped at 6) × the lane knob**. An idle machine is unchanged at 30s; a 5×-oversubscribed one gets 150s; a wedged one still reaches a verdict. Not a timed build, deliberately — these fixtures build in ~20ms and rebuild in ~1.7s whether the machine is idle or at load 50 (measured), so the wait is on chokidar's event, never on work. And every timeout now names **the elapsed time, the derivation, and the last rebuild event seen** (`last rebuild event: build #1 finished, 180059ms ago (1 started, 1 finished)`), which is what tells the starved watcher above apart from an honestly slow one — the starvation reproduces at load ~60 as *zero* rebuild events, so a bigger deadline is provably not its answer.
|
|
65
|
+
|
|
66
|
+
So the three suites live in `packages/web/test/watch/` and run in their own phase — `node --test --test-concurrency=1 test/watch/*.test.js`, after the parallel glob, one file at a time. That is the serialization rejected in #211's triage for costing wall time; the measured cost is ~20s, against a flake that twice blocked a ship gate. The directory IS the list (`watch-deadlines.test.js` reads it), so a new watcher suite cannot join one lane and be forgotten by the other. The deadline knob stays: it still covers an honestly slow rebuild.
|
|
67
|
+
|
|
68
|
+
**Skip knobs** (all documented in their script headers too): `OMEGA_SKIP_E2E=1` (sandbox e2e, verts, auth-token, extension auth, desktop auth, user flows), `OMEGA_SKIP_JOURNEY=1` (journey), `OMEGA_JOURNEY_STRICT=1` (unmet journey preconditions fail instead of skip), `OMEGA_JOURNEY_KEEP=1` (keep the temp brand after a green run).
|
|
69
|
+
|
|
70
|
+
**The emulator ready deadline scales the same way** ([#332](https://github.com/Omega-JS-Stack/omega/issues/332)): a boot's port sweep could stall on a network mount (lsof against a Time Machine volume), so every self-booting backend lane waits `OMEGA_EMULATOR_READY_TIMEOUT` ms (default 180000) for "All emulators ready" before declaring the boot dead. A junk value throws.
|
|
71
|
+
|
|
72
|
+
**The boot's port handling stopped paying for that stall, and says up front when it cannot work** ([#332](https://github.com/Omega-JS-Stack/omega/issues/332)). Both emulator sweeps (the pre-boot reaper and the post-shutdown orphan sweep) now bind-probe a port before asking lsof who holds it: a free port has no holder to name, so a normal boot shells out zero times instead of once per port. `omega test`'s "is an emulator already up" check is the same in-process probe. And every boot runs a PREFLIGHT before spawning firebase: after the allocator has bumped around whatever it can, any port the child still has to bind and somebody else holds fails the run immediately with a report naming each port and the dev stack to stop, rather than a stack that never comes up and a burned ready deadline. Every self-booting lane inherits it, because they all boot through the same `omega emulator` path.
|
|
73
|
+
|
|
74
|
+
**Publish rehearsal — `npm run release:check`** (scripts/release-check.js): packs every publishable (real prepare + vendoring), scratch-installs each tarball with local-tarball overrides for the published @omega.js runtime deps, `require.resolve`s it, and scans the installed tree for raw private @omega.js references. The laptop mirror of CI's pack-smoke; the mechanical gate for the publish-proving checkpoint. `--only=web,manager` iterates a subset; `--keep` preserves the scratch dir.
|
|
75
|
+
|
|
76
|
+
## The brand-shape corpus (cp197)
|
|
77
|
+
|
|
78
|
+
Tier 2's opening act ([scripts/corpus-shapes.js](../../scripts/corpus-shapes.js)): a matrix of brand SHAPES — target combos (web-only, default web+backend derivation, all-four, backend-only, desktop+extension), themes (classy, newsflash), content (a real `_posts` entry the blog must list) — each born through the REAL onboard in a temp dir (config validates, git initializes) and, for web cells, built the way `omega build` builds — `ensureTarget`, then `buildSite` with the whole asset lane, from the target root, writing a real `dist/` the invariants then READ (no installs; every dependency resolves from the monorepo) — with per-cell invariants: branded homepage, `data-theme-id`, `/blog`, sitemap + robots, font preloads. Rendering against a hand-built asset manifest instead proved the fixture rather than the product, and the font-preload list (a product of the compiled sheet) failed on every web cell until the stub went ([#776](https://github.com/Omega-JS-Stack/omega/issues/776)). Fully offline; failing cells keep their temp brand for autopsy. A new shape = a new `CELLS` row, never a new harness. One cell brings its own FIXTURE: `shape-theme-override` drops a consumer-local tier-2 theme over the seeded one before that same shared build runs, then reads every file kind the cascade resolves back out of `dist/` — layout, include, the theme scss entry, a section's html, a section's own js, the base js an `inherit: ['js']` folder left to the chain, and the packaged skin's `_theme.js` a shadowing theme inherits by shipping none ([#773](https://github.com/Omega-JS-Stack/omega/issues/773); the fixture is `scripts/corpus-fixtures/theme-override/`). The journey lane (below) covers the one axis this can't: the outside-monorepo install/boot/manage story.
|
|
79
|
+
|
|
80
|
+
## The wizard journey lane (cp195)
|
|
81
|
+
|
|
82
|
+
The scripted form of the cp194 hand rehearsal — proof that a consumer OUTSIDE the monorepo (where hoist-luck can't save anything) can live the whole story. Mechanics: `@omega.js/devkit/test/journey-harness` (spec-driven: `{ id, url, targets, expect }` — a corpus of brand shapes can reuse it); runner: [scripts/e2e-journey.js](../../scripts/e2e-journey.js).
|
|
83
|
+
|
|
84
|
+
- **Preconditions skip, never lie**: no network or no java → the lane prints SKIPPED and exits 0 (`OMEGA_JOURNEY_STRICT=1` turns that into a failure). `OMEGA_SKIP_JOURNEY=1` skips outright.
|
|
85
|
+
- **Runtime legs run creds-scrubbed**: `omega dev` and manage children get credential-shaped env vars stripped — the journey must never reach a real cloud. Install legs (onboard/link) keep the machine env.
|
|
86
|
+
- **The manage scorecard** comes from the brand's `.omega/runs/*.json`: `update` must succeed and no service may error except the allowed set (default `{testing}` — the live-URL probe of a never-deployed `.invalid` brand fails by design).
|
|
87
|
+
- **Artifacts**: numbered stage logs in `.temp/journey/`, per-step verdicts in `.temp/journey/steps.log` (`grep '^FAIL'` names the leg that broke, including a `preflight` abort that never reached a step); on failure the temp brand is KEPT and its path printed (`OMEGA_JOURNEY_KEEP=1` keeps it on success too).
|
|
88
|
+
- Heavy by design (registry installs, all-four app builds) — that's the point; it caught brand-root manager resolution (#8), the ambient-Node engines stamp (#9), and the stale-manifest clobber + linked-prepare destruction (#10) on its first runs.
|
|
89
|
+
|
|
90
|
+
# Test scoping (`omega test`) — the C5 grammar
|
|
91
|
+
|
|
92
|
+
One grammar, every framework (parser: `@omega.js/devkit/test/scope`, adopted by the devkit runner-core → desktop + extension, the backend runner, and web's test command). Decided by Ian 2026-07-11 (core-changes inbox C5): **a bare test run from a brand/app never drags the framework's suite in** — the framework suite is always an explicit choice.
|
|
93
|
+
|
|
94
|
+
## Grammar
|
|
95
|
+
|
|
96
|
+
| Target | Runs | Notes |
|
|
97
|
+
|--------|------|-------|
|
|
98
|
+
| *(bare)* | **project tests only** | consumer default; `pages/x` = project tests under that path |
|
|
99
|
+
| `project:` / `brand:` | project tests only | explicit spelling; optional path: `project:auth/` |
|
|
100
|
+
| `framework:` / `omega:` / `mgr:` | the framework's own suite | universal aliases |
|
|
101
|
+
| `backend:` `web:` `desktop:` `extension:` (+ legacy `em:` `bxm:` `ujm:`) | the framework's own suite | per-framework ids |
|
|
102
|
+
| `full:` | both sources | optional path applies to both: `full:auth` |
|
|
103
|
+
|
|
104
|
+
- Paths after a prefix scope within that source: `framework:routes/general`, `project:checkout`.
|
|
105
|
+
- Unknown prefixes (`framwork:x`) warn and are ignored — never silently match nothing.
|
|
106
|
+
- Multiple targets union sources; each path binds to its own source.
|
|
107
|
+
|
|
108
|
+
## The self-test exception
|
|
109
|
+
|
|
110
|
+
Inside a framework package itself (cwd package name === the framework), a bare run means the framework's own suite — each package's `npm test` keeps meaning "run my suite". Consumer context is what flips to project-only.
|
|
111
|
+
|
|
112
|
+
## Corpus spelling
|
|
113
|
+
|
|
114
|
+
The sandbox brand's backend corpus is the framework suite run in consumer context — its scripts say so explicitly since cp94: `npx omega test framework:` ([brands/sandbox-brand/targets/backend/package.json](../../brands/sandbox-brand/targets/backend/package.json)).
|
|
115
|
+
|
|
116
|
+
## Per-framework notes
|
|
117
|
+
|
|
118
|
+
- **web** — project scope = production build + smoke checks (pages rendered, themed 404, the main bundle on disk, every internal link in `dist/**/*.html` resolving — [#430](https://github.com/Omega-JS-Stack/omega/issues/430) — and the four fast audit scans of the same dist: page meta, anchor fragments, image `alt`, sitemap orphans, each opt-out-able per page in the shared `config/link-exceptions.json5` — [#468](https://github.com/Omega-JS-Stack/omega/issues/468)) + consumer `test/`; `framework:` runs @omega.js/web's own `node --test` suite. That suite is local-era only: it requires the package's unbuilt `src/` and its dev dependencies, neither of which a published install receives, so `framework:` from an installed brand finds nothing ([#115](https://github.com/Omega-JS-Stack/omega/issues/115) ruling: the suite's home is the monorepo, and shipping it would cost every consumer ~1 MB for a scope only framework developers use).
|
|
119
|
+
- **backend** — framework source = the routes/events/rules corpus (boot/ stays self-test-only); project source = `<project>/test`. Filters are prefix-stripped centrally, so `framework:routes/general` and a bare `general/` (project) match within their own trees only. Unlike web's, this suite RUNS from a consumer in both install modes — local link and a plain npm install ([#720](https://github.com/Omega-JS-Stack/omega/issues/720)): the package ships `test/` as raw source beside `dist/`, and every case file reaches the framework through `dist/` (the 1:1 mirror of `src/`, which does not ship) and the vendored devkit/config/account under `dist/vendor/` (those packages never publish). Deliberate divergence from desktop/extension, whose suites ride INSIDE `src/test/` and therefore inside `dist/`; backend's live at the package root, so `test/` earns its own `files` entry. `test/boot/suite-portability.test.js` fails the build the day a case file reaches for `../../src/…` or a devDependency again.
|
|
120
|
+
- **desktop / extension** — runner-core handles sources + layers (`--layer build|main|renderer|boot` etc. unchanged, orthogonal to scoping).
|
|
121
|
+
- **Every runner-core framework** — framework `boot/` suites are self-test only: they assert on the framework's own fixture consumer, so consumer discovery excludes them and consumers write their own under `<app>/test/boot/`. A consumer run that asks for `--layer boot` with no suites of its own says so instead of running silently empty.
|
|
122
|
+
|
|
123
|
+
## Brand root (cp94b)
|
|
124
|
+
|
|
125
|
+
At a **brand root** (a directory carrying `config/omega.json5` with no framework declared nearer), every framework's `omega` bin hands over to `@omega.js/manager` — the omega-bin dispatcher detects the brand the same way it detects targets (nearest context wins, walking up; the rule twins `@omega.js/config`'s `resolveBrandRoot`). The manager's `test` command then fans out over the brand's target-mapped dirs, spawning each target's own framework bin with `cwd` = the target dir:
|
|
126
|
+
|
|
127
|
+
| At the brand root | Runs |
|
|
128
|
+
|-------------------|------|
|
|
129
|
+
| `npx omega test` | every target's **project tests** (bare per target) |
|
|
130
|
+
| `npx omega test framework:` | every target's framework suite |
|
|
131
|
+
| `npx omega test full:` | both sources, every target |
|
|
132
|
+
| `npx omega test routes/x` | project filter forwarded to every target (a target that does not carry it is a no-op; a path NO target carries fails the run, [#814](https://github.com/Omega-JS-Stack/omega/issues/814)) |
|
|
133
|
+
| `npx omega test web:pages/` | ONLY the web target — its framework suite, scoped |
|
|
134
|
+
| `npx omega test em:` | ONLY the desktop target's framework suite |
|
|
135
|
+
|
|
136
|
+
- Universal targets (bare paths, `framework:`/`omega:`/`mgr:`, `full:`, `project:`/`brand:`) forward to every target dir verbatim; per-framework ids route to the target owning that framework. The id → framework map is `FRAMEWORK_IDS` in `@omega.js/devkit/test/scope` — the same SSOT each framework's runner reads its own aliases from.
|
|
137
|
+
- An id with no matching target warns and runs nothing (exit 0 — same semantics as a target-level filter matching no tests). Only-invalid targets fall back to bare-everywhere, mirroring the target-level parser.
|
|
138
|
+
- That exit 0 holds for a bare prefix only. An id carrying a PATH whose framework this brand has no target for (`omega test desktop:renderer/typo` in a website-only brand) keeps the warning AND fails: zero runs plus a named path is the same typo the target-level rule catches ([#814](https://github.com/Omega-JS-Stack/omega/issues/814)).
|
|
139
|
+
- A forwarded path a target does not carry is a **no-op there, not a failure**: the manager sets `OMEGA_TEST_FANOUT=1` on every forwarded run, and a target CLI whose target selected nothing answers with the distinct `NO_MATCH_EXIT_CODE` (3) instead of 1 (both live in `@omega.js/devkit/test/scope`). The fan-out counts those as misses, and fails the brand run only when EVERY target missed, printing `No test file matches "<target>"` in the manager's own log tag. A standalone run in a target dir, with no signal, still exits 1 ([#814](https://github.com/Omega-JS-Stack/omega/issues/814)).
|
|
140
|
+
- Targets run **sequentially** with streamed output; any failing target makes the whole run exit 1 (per-target summary at the end).
|
|
141
|
+
- **Flags are not fanned out** (`--layer`, `--extended`, …) — flagged runs are target-level invocations; run them from the target dir.
|
|
142
|
+
- Other manager commands ride the same handoff: bare `omega` at a brand root means the manager's manage cycle, `omega onboard` reaches the wizard, and `omega deploy` is the brand-root deliberate-deploy fan-out (docs/shared/deploys.md).
|
|
143
|
+
|
|
144
|
+
## CI runner notes
|
|
145
|
+
|
|
146
|
+
- **npm 11 script-approval gating skips dependency postinstalls on CI runners.** Puppeteer's Chrome download is handled explicitly in ci.yml (`npx puppeteer browsers install chrome`); if a native-postinstall dep (electron, canvas, sharp, …) ever misbehaves in CI, this gating is the first suspect — add an explicit install step like puppeteer's rather than disabling the gate.
|
|
147
|
+
- **Post-mortem steps use `if: always()`**, not `if: failure()` — runner-level cancellation (OOM, watchdog) is NOT `failure()`, and cancelled runs are exactly the ones that need the diagnostics.
|