@astrale-os/sdk 0.6.0-beta.39 → 0.6.0-beta.40

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.
@@ -0,0 +1,155 @@
1
+ # SDK release lifecycle
2
+
3
+ Release Please owns the versions and GitHub releases for the SDK package set:
4
+
5
+ - `@astrale-os/sdk`
6
+ - `@astrale-os/adapter-cloudflare`
7
+ - `@astrale-os/adapter-astrale`
8
+ - `create-astrale-domain`
9
+
10
+ The Node workspace plugin follows local dependency edges. Every internal release edge uses
11
+ `workspace:*`, so a changed SDK refreshes the dependent adapter and scaffolder packages in the same
12
+ Release Please pull request and publication rewrites each edge to that dependency's exact version.
13
+ Never use a caret or tilde for an internal release edge: an immutable adapter must not resolve a
14
+ newer SDK or adapter after publication. The shared publisher consumes those versions; it never
15
+ invents a release version.
16
+
17
+ Dependency installation, authoritative publication, and public qualification are npmjs-only. npm
18
+ publication uses OIDC Trusted Publishing. After that proof completes, the workflow mirrors the exact
19
+ SDK and adapter tarballs to private GitHub Packages copies. GitHub Packages is never a consumer,
20
+ build, installation, or qualification source. Never add a PAT, an npm token, an auth file, or a
21
+ repository package token; no credential may enter a tracked file, lockfile, package archive, or build
22
+ log.
23
+
24
+ ## Normal beta release
25
+
26
+ Prerequisite: every exact Kernel dependency in the SDK manifest is already published on npm and the
27
+ SDK passes typecheck, test, build, pack, and fresh-consumer gates against those artifacts. Never use
28
+ a local checkout, tarball, or override to make the release build pass.
29
+
30
+ 1. Merge conventional feature and fix pull requests into `main`.
31
+ The squash-merge title is Release Please input: use `feat` for a public capability or API change
32
+ and `fix` for a behavioral defect. Never classify a publication-bearing change as `refactor`,
33
+ `docs`, `chore`, or `test`; those types do not advance the beta
34
+ package cohort.
35
+ 2. The `Release` workflow creates or refreshes the single Release Please pull request.
36
+ 3. Select **Approve workflows to run** for that bot-authored pull request and wait for every required
37
+ check. GitHub can mark these workflows `action_required`; this is an approval gate, not a passed
38
+ run.
39
+ 4. Review every changed package version, exact internal dependency, changelog, and
40
+ `.release-please-manifest.json` entry.
41
+ 5. Merge the Release Please pull request.
42
+ 6. Release Please creates an immutable GitHub prerelease for every released component. Only then does
43
+ `Release` dispatch the standalone `Publish` workflow with the exact released commit SHA, preserving
44
+ its npm Trusted Publishing identity. Publish rejects a non-SHA or a checkout mismatch before npm I/O.
45
+ 7. The shared package publisher builds the workspace and publishes every public package to npm. A
46
+ `-beta.N` version advances npm `beta`; npm `latest` remains on the last stable version.
47
+ 8. The same workflow then runs `pnpm run qualify:publications` without a token. This creates isolated
48
+ temporary stores, installs the four exact manifest versions from npmjs, generates a Domain with
49
+ `linkWorkspacePackages: false`, pins that generated project to the exact SDK and adapter versions,
50
+ and runs its typecheck, tests, build, and package command. Any missing publication, transitive
51
+ dependency, wrong version, non-exact internal cohort edge, local source, override, or GitHub
52
+ registry reference fails the workflow.
53
+ 9. A separate least-privilege job downloads the exact SDK and adapter tarballs from npm, verifies
54
+ their npm SHA-512 integrity, mirrors them with `GITHUB_TOKEN`, and then requires byte-identical
55
+ GitHub downloads, matching release-channel tags, private visibility, and an exact link to
56
+ `astrale-os/sdk`. The scaffolder remains npm-only.
57
+
58
+ The publisher receives package directories in strict producer order: SDK, Cloudflare adapter,
59
+ Astrale adapter, then scaffolder. The shared action validates this dependency order and completes npm
60
+ plus the exact release-channel dist-tag for one package before it starts the next. A linked workspace
61
+ build is therefore never treated as publication proof.
62
+
63
+ npm publication has no token fallback. All four npm packages must configure GitHub Actions Trusted
64
+ Publishing for this repository and the `publish.yml` workflow. A missing Trusted Publisher or a wrong
65
+ dist-tag is an explicit, fail-closed publication error; do not work around it with a PAT or npm token.
66
+
67
+ The mirror job starts only after the anonymous npm qualification succeeds. It has `packages: write`
68
+ but no npm OIDC permission, performs no build or workspace install, and cannot publish to npm. A
69
+ mirror failure makes `Publish` red without undoing the already-complete npm release. Rerunning the
70
+ failed job is safe: existing versions must be byte-identical, no version is overwritten or deleted,
71
+ and tags are reconciled only when npm still points them at that exact version. Do not backfill older
72
+ version gaps during initial rollout; the next ordinary beta release is the admission proof.
73
+
74
+ Ordinary source pushes do not publish. Do not manually edit package versions, manually dispatch
75
+ `Publish`, or push release tags during the normal flow.
76
+
77
+ ## Start the new Auth beta series
78
+
79
+ The session-owned Auth API starts a new SDK beta series at `0.6.0-beta.0`. Its feature PR sets
80
+ only the SDK's package-local `release-as` to that version. Release Please owns the actual manifest
81
+ and cohort bumps. Remove that one-shot override from the generated release PR before merging it.
82
+ This keeps the release on npm `beta`, while existing SDK `0.5.0` prerelease ranges retain the old API.
83
+
84
+ ## Promote the package set to stable
85
+
86
+ Create one promotion pull request that removes only these beta settings from
87
+ `.release-please-config.json`:
88
+
89
+ ```json
90
+ "versioning": "prerelease",
91
+ "prerelease": true,
92
+ "prerelease-type": "beta"
93
+ ```
94
+
95
+ Keep the Node workspace plugin, `always-update`, package map, and workflows unchanged. In the same
96
+ promotion pull request, temporarily add these package-local overrides:
97
+
98
+ ```json
99
+ "packages": {
100
+ ".": { "component": "sdk", "release-as": "0.6.0" },
101
+ "adapter-cloudflare": { "component": "adapter-cloudflare", "release-as": "0.5.0" },
102
+ "adapter-astrale": { "component": "adapter-astrale", "release-as": "0.5.0" },
103
+ "create-astrale-domain": {
104
+ "component": "create-astrale-domain",
105
+ "release-as": "0.3.0"
106
+ }
107
+ }
108
+ ```
109
+
110
+ After that promotion pull request lands, Release Please proposes those stable versions. Remove all
111
+ four `release-as` properties from the generated stable release pull request, rerun its checks, and
112
+ merge it. This publishes npm `latest` and ordinary GitHub releases. The npm `beta` tags intentionally
113
+ remain on the last beta and do not affect bare installs.
114
+
115
+ ## Verification
116
+
117
+ Always name npmjs explicitly when inspecting releases so the verification also proves the public
118
+ registry boundary.
119
+
120
+ ```bash
121
+ gh run list --repo astrale-os/sdk --workflow Release --limit 5
122
+ gh run list --repo astrale-os/sdk --workflow Publish --limit 5
123
+ npm view @astrale-os/sdk dist-tags --json --registry=https://registry.npmjs.org
124
+ npm view @astrale-os/adapter-cloudflare dist-tags --json --registry=https://registry.npmjs.org
125
+ npm view @astrale-os/adapter-astrale dist-tags --json --registry=https://registry.npmjs.org
126
+ npm view create-astrale-domain dist-tags --json --registry=https://registry.npmjs.org
127
+ gh api orgs/astrale-os/packages/npm/sdk --jq '{visibility,repository:.repository.full_name}'
128
+ gh api orgs/astrale-os/packages/npm/adapter-cloudflare --jq '{visibility,repository:.repository.full_name}'
129
+ gh api orgs/astrale-os/packages/npm/adapter-astrale --jq '{visibility,repository:.repository.full_name}'
130
+ ```
131
+
132
+ The SDK, adapters, scaffolder, generated manifest, and every transitive runtime dependency must be
133
+ installable from npmjs without authentication. Prove that boundary from a clean temporary directory:
134
+
135
+ ```ini
136
+ # ./npmrc (in the smoke-test parent directory)
137
+ registry=https://registry.npmjs.org/
138
+ always-auth=false
139
+ ```
140
+
141
+ Then run the complete smoke test:
142
+
143
+ ```bash
144
+ NPM_CONFIG_USERCONFIG="$PWD/npmrc" npm install @astrale-os/sdk@beta
145
+ NPM_CONFIG_USERCONFIG="$PWD/npmrc" npx -y create-astrale-domain@beta beta-smoke --yes
146
+ cd beta-smoke
147
+ NPM_CONFIG_USERCONFIG="../npmrc" pnpm install --no-frozen-lockfile
148
+ pnpm typecheck
149
+ pnpm test
150
+ pnpm build
151
+ ```
152
+
153
+ Inspect the generated lockfile and installed package manifests. They must resolve the released SDK and
154
+ adapter package set and the SDK-owned public Kernel dependencies from npmjs, not `latest`, GitHub
155
+ Packages, workspace links, Git URLs, or an older manually published beta.
@@ -0,0 +1,124 @@
1
+ # What a compiled Schema costs
2
+
3
+ A compiled Domain is large, and its size is not driven by how much the Domain declares. This
4
+ records what the weight is made of, where it is paid, and which levers belong to this repository.
5
+
6
+ Every figure comes from `scripts/bench/`, on synthetic Domains built around `KernelSchema`. Node
7
+ counts and byte weights follow from the compiled format and hold anywhere. **Timings do not.** The
8
+ benches run under Bun, which is JavaScriptCore, and workerd is V8. On the same graph and the same
9
+ walk:
10
+
11
+ | walk | V8 (Node 26) | JavaScriptCore (Bun) |
12
+ | --- | --- | --- |
13
+ | seal, as shipped | 10.4 ms | 73.7 ms |
14
+ | seal, arrays indexed and primitives skipped | 5.9 ms | 48.2 ms |
15
+
16
+ Close to an order of magnitude apart. Read every millisecond below as a JavaScriptCore
17
+ millisecond: useful for ranking two strategies against each other, worthless as a production
18
+ figure. Any claim about cold start has to be re-measured on the real bundle in workerd.
19
+
20
+ ## The cost law
21
+
22
+ Growing a Domain two ways, plain classes against classes carrying one format-constrained
23
+ property (`bun scripts/bench/schema-cost.ts`):
24
+
25
+ | classes | plain | one constrained property each |
26
+ | --- | --- | --- |
27
+ | 0 | 19 522 | 19 522 |
28
+ | 5 | 19 587 | 22 157 |
29
+ | 20 | 19 782 | 30 062 |
30
+ | 50 | 20 172 | 45 872 |
31
+
32
+ Three readings, each counter-intuitive.
33
+
34
+ **An empty Domain is not empty.** 19 522 nodes and 554 KiB before anything is declared. That is
35
+ the floor the `KernelSchema` dependency sets, and every Astrale Domain pays it.
36
+
37
+ **Classes and properties are nearly free.** Fifty classes and a hundred plain properties add a
38
+ thousand nodes, about twenty per class. The size of the model is not the problem.
39
+
40
+ **One thing is expensive: a string carrying a format.** The same constraint on fifty classes adds
41
+ 26 350 nodes, about **527 nodes per property**, and fifty separate automata for one expression.
42
+ `resolve` and `compile` both roughly double over the same range.
43
+
44
+ ## Where the weight is
45
+
46
+ On the Kernel floor, by region (`bun scripts/bench/compiled-graph.ts`):
47
+
48
+ | region | nodes | share |
49
+ | --- | --- | --- |
50
+ | `values[].patterns` | 15 856 | 81.2 % |
51
+ | `values[].nodes` | 3 031 | 15.5 % |
52
+ | `definitions.*` | 563 | 2.9 % |
53
+ | everything else | 6 | 0.0 % |
54
+
55
+ The weight is compiled regular-expression automata. A bounded quantifier is unrolled into one
56
+ state per repetition, so `{64}` becomes 162 states and `{0,253}` becomes 528. Each state carries
57
+ two arrays, each transition an array of ranges, and each range is itself a two-element array.
58
+ That is also why a compiled graph holds more arrays than objects.
59
+
60
+ Nothing is shared between copies. Sixteen automata stand for seven distinct expressions:
61
+ `sha256:[0-9a-f]{64}`, nineteen characters, weighs 576 nodes and exists five times over. Interning
62
+ by source would remove 7 973 of the 15 794 pattern nodes at identical semantics, since two
63
+ automata for one expression are structurally identical and immutable.
64
+
65
+ ## Where it is paid
66
+
67
+ 1. **At build time**, in `compile`. Once, on a contributor's machine.
68
+ 2. **In the Worker bundle.** The material is emitted as an inline literal, so the automata ship as
69
+ bytes: 44 % of a bare compiled Schema, 65 % once a Domain constrains its strings.
70
+ 3. **At cold start, the material path.** The emitted literal is allocated, sealed, and realized on
71
+ the first request of every isolate.
72
+ 4. **At cold start, the authoring path**, when application code builds the Schema from source at
73
+ runtime. `resolve` is memoised per Schema value, so it is a once-per-isolate cost like the
74
+ material path, not a per-request one.
75
+ 5. **In any client** that builds the Schema, a browser included.
76
+
77
+ ## Sealing the material
78
+
79
+ `schema.compiled` is almost the whole material graph, so sealing cost is a question of how many
80
+ times that graph is walked (`bun scripts/bench/freeze-strategies.ts`):
81
+
82
+ | strategy | cost |
83
+ | --- | --- |
84
+ | before: two SDK passes and one DSL pass | 131.6 ms |
85
+ | current: one SDK pass and one DSL pass | 74.7 ms |
86
+ | candidate: the DSL owns `schema.compiled` | 62.0 ms |
87
+
88
+ Two results matter more than the ranking.
89
+
90
+ **Most of the remaining cost is not the Schema.** Sealing `schema.compiled` alone is 17.2 ms.
91
+ Sealing `schema.bundle.bytes` alone is 43.9 ms, because it is one array of tens of thousands of
92
+ numbers and the walker calls `Object.values` on it. A walker that indexes arrays in place and
93
+ never recurses into a primitive takes one pass from 61.2 ms to 43.5 ms. `Object.freeze` on an
94
+ array of numbers already seals its elements, so that recursion changes nothing: it is cost
95
+ without a guarantee behind it.
96
+
97
+ **The emitter throws away sharing the compiler produced.** The compiled graph holds 19 522 nodes
98
+ as built and 32 882 after a JSON round trip, because JSON has no references. 13 360 nodes, two
99
+ thirds again, exist only because the material is emitted as a flat literal. Hoisting identical
100
+ subtrees into shared bindings at emission would restore sharing rather than invent it, and would
101
+ cut bundle bytes at the same time.
102
+
103
+ Sealing is a guarantee, not decoration: `realize()` retains live references into the graph, and
104
+ the material is one singleton shared by every concurrent request in the isolate. None of the
105
+ changes above give that up. They stop walking what is already sealed, or what sealing a container
106
+ already covers.
107
+
108
+ ## Which levers are here
109
+
110
+ Owned by this repository: how many times the material is walked, how the walker treats a flat
111
+ array, whether the emitter shares identical subtrees, and whether the SDK hands out the Domain
112
+ that a restored Build already carries instead of leaving an application to rebuild it.
113
+
114
+ Owned by `kernel-dsl`: the cost of `resolve`, the pattern table in the compiled format, and the
115
+ unrolling of bounded quantifiers. The last of these pays at all five sites at once and is where
116
+ most of the weight is. None of it can be reached from here.
117
+
118
+ ## Verifying a change
119
+
120
+ `scripts/bench/freeze-equivalence.ts` compares the frozen state of every reachable node between
121
+ two sealing implementations and exits non-zero on a divergence or a vacuous walk. Spine assertions
122
+ cannot tell a deep seal from a shallow one: `material()` returns a shallow-frozen envelope, so a
123
+ walker treating a frozen container as proof of a sealed interior would seal nothing and still
124
+ satisfy them. `src/deployment/build/__tests__/realization.test.ts` carries the standing audit.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astrale-os/sdk",
3
- "version": "0.6.0-beta.39",
3
+ "version": "0.6.0-beta.40",
4
4
  "description": "Schema-first SDK for defining, composing, and deploying Astrale domains",
5
5
  "keywords": [
6
6
  "astrale",
@@ -20,7 +20,8 @@
20
20
  },
21
21
  "files": [
22
22
  "dist",
23
- "!dist/**/*.map"
23
+ "!dist/**/*.map",
24
+ "docs"
24
25
  ],
25
26
  "type": "module",
26
27
  "sideEffects": false,