page:docs-specs-platform-gaps-40-packaging
Spec 40 — Packaging (WP-PKG-*) reference
Date: 2026-09-27 · Status: Gate-B draft · Binding decisions: D3 (all three packaging forms, spec proposes priorities — docs/specs/platform-gaps/00-intent.md:23), executed PRIVATE-first under D17/D19/D21 (00-intent.md:42-46), constrained by D10 (break freely, major bump), D11 (packaging follows migration + coverage in the roadmap), D13 (mobile deferred), D15 (attribution).
Spec 40 — Packaging (WP-PKG-*)
Date: 2026-09-27 · Status: Gate-B draft · Binding decisions: **D3** (all three packaging forms, spec proposes priorities — docs/specs/platform-gaps/00-intent.md:23), executed **PRIVATE-first under D17/D19/D21** (00-intent.md:42-46), constrained by D10 (break freely, major bump), D11 (packaging follows migration + coverage in the roadmap), D13 (mobile deferred), D15 (attribution).
Inputs consumed in full: R3 research/packaging-state.md (the packaging dossier; cited R3 §n with underlying path:line), Gate-A synthesis research/synthesis-adoption-proposal.md (§1.5 packaging-form priority, §4.4 planned 13-WP inventory), spec 15 15-private-isolation-rename.md (its §2 scope, §3 name matrix, §4 registry/guards, and §5 interaction rules **govern all distribution work in this spec**), spec 10 10-t3code-migration.md (cross-refs only: unit row 12 at 10-t3code-migration.md:46 hands install/service/self-update landing to this spec; WP-T3-19/26/28/31/32/33 are consumed as dependencies; WP-T3-31's relay ops story is productized by WP-PKG-14), spec 20 20-functionality-coverage.md (cross-refs only: WP-FN-02 AC4 / WP-FN-03 AC2 hand default key provisioning to this spec's WP-PKG-04/05; WP-FN-16 FR-7 hands relay rollout to WP-PKG-14).
Path conventions: fabric paths relative to C:/work/fabric; the t3code MIT snapshot is cited t3:<path> (snapshot location per 00-intent.md:51). Spec 30 (30-testing.md) is not yet authored; its work packages are referenced as WP-TEST-<nn> under the fixed mapping Q-nn → WP-TEST-nn from the Gate-A inventory (research/synthesis-adoption-proposal.md §4.3) — e.g. WP-TEST-04 is Q-04, the whole-platform packaged-install e2e.
**Naming discipline**: this spec introduces **no** package, binary, env prefix, directory, or chart name outside the spec 15 §3 locked matrix or its stated derivation rules. Two derived-name additions are **proposed for the Gate-B name lock** (§3.6 launcher alias, §2.4 npm platform packages) and are explicitly flagged as matrix amendments, not minted here.
---
1. Scope, private-first doctrine, and priority order
1.1 The three forms (D3) and what exists today
| Form | D3 wording | Fabric today | Gap class |
|---|---|---|---|
| **Form 1 — single-node installer UX** | curl\ | sh / winget / npx, service, self-update | npm-global only, Node-gated (docs/user-guide/getting-started/installation.md:126-131); **no** installer scripts, binary archives, service, self-update, or uninstaller anywhere in fabric (R3 §1.5, negative search recorded at research/packaging-state.md:150-161) |
| **Form 2 — cluster/Helm bundle** | versioned platform chart, GKE path | Chart exists, production-shaped, but frozen at version: 0.1.0 vs product 6.0.x (packages/kradle/charts/Chart.yaml:1-7 vs package.json:3), never published (only inert helm push in the non-executing nested workflow packages/kradle/core/.github/workflows/publish.yml:94, R3 inferred finding 1), consumed from source path by CI (.github/workflows/publish.yml:952,1080); the platform deploys via **three mechanisms** (chart + atlas Kustomize overlay publish.yml:740-742 + Envoy Kustomize Chart.yaml:31-34) | Productization — versioning, publication, bundling |
| **Form 3 — npm SDK surface** | stable publics, semver, docs | Strong publish integrity (single resolver, waves, packed-artifact verifier — R3 §1.1) but **no declared publics**: @a5c-ai/babysitter-sdk has zero exports entries and five bins (packages/babysitter-sdk/package.json:7-14), @a5c-ai/adapters exports only "." (R3 §1.6); lockstep 6.0.x with no per-package semver signal; changesets configured but dead (.changeset/config.json:1-20, no workflow references — R3 §1.1) | Boundary definition + versioning posture |
1.2 Private-first doctrine (D17/D19 — binding on every WP below)
The Gate-A synthesis pre-dates amendment 2026-09-27b; this spec applies the amendment throughout:
1. **Every distribution channel in this spec is private.** npm packages publish only to the GCP Artifact Registry npm repo fabric-npm (https://us-central1-npm.pkg.dev/boot-464019/fabric-npm/, spec 15 §4.1). Renamed @fabricstack/* packages are **never** published to public npmjs (D19; guards G1–G6, spec 15 §4.3). 2. **Non-npm artifacts get private homes too** (§1.3): binary archives, desktop installers, and Helm charts publish to Artifact Registry repositories in the same GCP project boot-464019 — not to public GitHub Releases, not to ghcr.io, not to winget/Homebrew/AUR (this drops the public-channel half of the planned P-08; justification in §1.5). 3. **CI/publish isolation from the public babysitter pipelines**: the public a5c-ai/babysitter repo keeps publishing @a5c-ai/* to public npmjs untouched (D17); fabric's packaging pipelines are the private train of spec 15 §4.2 (publish-npm-private.yml, tags fabric/<branch>/v<version>), and no workflow this spec adds may reference the public repo as a push target (WP-RN-02 guard) or registry.npmjs.org/ghcr.io in a publish context (WP-PKG-13 extends G2 to non-npm artifacts). 4. **All packaging operates on the renamed matrix**: package names, bins, env prefixes (FABRIC_* for platform-wide installer variables, per spec 15 §2/§3.4), data dirs (~/.fabric/, spec 15 §3.5), and chart names (control-plane, spec 15 §3.6) come from spec 15 §3.
1.3 Artifact-home decision table (the split-repo constraint, R3 spec input 9)
R3 spec input 9 requires every new artifact channel to "pick a home repo explicitly and keep the single-resolver version discipline". Spec 15 §4.2 already settled npm bytes ("private npm artifacts live in AR under boot-464019; spec 40 inherits that decision"). This spec decides the rest:
| Artifact class | Home (decided) | Why |
|---|---|---|
| npm packages (43 renamed + §2.4 platform packages) | AR npm repo fabric-npm (spec 15 §4.1) | inherited from spec 15 §4.2 |
Self-contained per-platform archives + SHA256SUMS (Form 1) | **New AR generic-format repo fabric-dist**, project boot-464019, region us-central1 | Same project/region/WIF auth as the existing Docker AR (publish.yml:3-6,880-893, R3 §1.3); AR generic repos serve versioned binary blobs over authenticated HTTPS, which is exactly what a private installer needs. Alternatives rejected: *private GitHub Releases on a5c-ai/fabric* — asset download on private repos requires GitHub API tokens with repo scope, coupling customer machines to GitHub org membership; *GCS bucket* — loses AR's IAM/version/immutability semantics and would be the only artifact class outside AR; *public GitHub Releases* — violates D19's private-distribution posture outright |
| Desktop installers + update manifests (console-desktop, WP-T3-26) | Same fabric-dist repo, desktop/ artifact path family | One channel, one auth story; t3code's merged mac/win update manifests (t3:scripts/merge-update-manifests.ts:19-24, R3 §2.3) point at the same base URL the archives use |
Helm chart control-plane | **AR OCI**: oci://us-central1-docker.pkg.dev/boot-464019/fabric-charts (new AR Docker-format repo fabric-charts; Helm OCI charts are OCI artifacts) | The precedent in-tree is the inert ghcr push (packages/kradle/core/.github/workflows/publish.yml:94, R3 §1.2) — **rejected**: ghcr is public-facing and belongs to the public repo's org surface. AR OCI reuses WIF auth and keeps the chart beside the container images the chart references |
| Container images | Existing AR Docker repo at us-central1-docker.pkg.dev (publish.yml:880-893) | Already private and working; WP-PKG-09 adds platform-version tags beside the commit-sha tags (publish.yml:812-816) |
| Relay deployables (Cloudflare Worker bundle + Postgres migrations + cloudflared config, WP-T3-31) | **Deployed, not distributed**: the fabric-controlled Cloudflare account (Workers) and its managed Postgres — no customer-facing artifact exists for this class | The relay is a hosted service fabric operates under the broker-not-proxy trust model (WP-FN-16 FR-2); its "home" is the deployment target itself. Sources live in infra/relay (10-t3code-migration.md:476); deploy provenance is the train tag; pipeline, promotion, and guard coverage are WP-PKG-14. Publishing the Worker to any public marketplace/registry is excluded (PG1 extension, WP-PKG-14 AC5) |
All four artifact-registry homes are inside boot-464019, authenticated by the existing WIF step (google-github-actions/auth@v2, R3 §1.3) in CI and by gcloud ADC or short-lived tokens for humans/installers. The relay's deployment target is the one deliberate non-AR home — a deploy destination, not a distribution channel — credentialed separately under the PG5 pattern (WP-PKG-14 AC6).
1.4 Node floor encoding (binding; synthesis risk 3, R3 spec input 5)
| Floor | Value | Where encoded |
|---|---|---|
| Runtime floor, npm-installed surfaces | >=22.16 workspace engines (WP-T3-32 stage 1; the strictest already-landed floor), which subsumes the node:sqlite floor >=22.5 (installation.md:199-202; R2 §15 via 10-t3code-migration.md:483) | root package.json engines + per-package engines; CI engines check (WP-T3-32 AC5) |
| Runtime floor, Form 1 archives | **None for the consumer** — the SEA executables embed their runtime; "nothing in it may require Node, npm, or a compiler on the machine that unpacks it" (t3:scripts/build-cli-archive.ts:1-16, adopted as the archive contract) | WP-PKG-03 AC1 |
| SEA **build hosts** | >=25.7 (R3 spec input 5; synthesis risk 3) | release-pipeline build-job Node matrix, WP-PKG-03 AC6 |
1.5 Priority order (D3 "spec proposes priorities") and planned-inventory delta
**Decision proposed: Form 3 (npm SDK publics) → Form 1 (single-node installer/service/self-update) → Form 2 (chart productization), with the private-registry substrate (WP-RN-03/04/07) preceding all three.**
Rationale (inheriting synthesis §1.5, restated under the amendment):
- **Form 3 first** because its blocker — no exports maps, unbounded publics (R3 §1.6) — is a *prerequisite* for D6's ACP re-platforming (new lane packages need defined publics to land against) and because spec 15 §5 rule 6 binds it to the RN-1 train: "P-01's exports-map program and the D10 major bump land in the same train as RN-1 so publics are defined once, under the new names". Doing it later means defining publics twice.
- **Form 1 second**: it starts from zero (R3 §1.5) but has a complete, tested t3code blueprint (R3 §2) and its central deliverable — the environment server — only exists after WP-T3-19; the adopted desktop app (WP-T3-26) amplifies it. Under D17 the single-node private product is the commercial surface, which ranks it above Form 2.
- **Form 2 last**: the chart + GKE pipeline already work internally (R3 §1.2–1.3); what remains is productization (versioning, OCI publication, bundling), and spec 15 already schedules the chart's rename for phase RN-5 — coordinating WP-PKG-09 with WP-RN-12 "so re-versioning and renaming land as one chart-consumer break, not two" (spec 15 WP-RN-12 dependency note) naturally places it last.
- Consistent with D11: all of this follows the t3code migration and coverage work in the roadmap; nothing here blocks WP-T3-* except where spec 10 itself declared the dependency (WP-T3-28's bootstrap-download activation gates on WP-PKG-03 —
10-t3code-migration.md:451-453).
**Delta against the planned 13-WP inventory** (synthesis §4.4; count becomes **14** — three scope adjustments plus one addition):
1. P-01's bin-collision half moved to spec 15 (WP-RN-06 + guard G5 own it); WP-PKG-01 keeps exports maps/publics and *consumes* the collision-free bin set. 2. P-08's public OS package managers (winget/brew/AUR/deb) are **dropped**: they are public distribution channels and contradict D19's private posture (the synthesis §4.4 row pre-dates amendment 2026-09-27b). WP-PKG-08 keeps the uninstall story and records the no-public-channel decision. If a future decision opens a public channel for the private product, that is a new intent amendment, not a packaging WP. 3. P-13 narrows to non-npm artifact channels + guard extension, because spec 15 §4.2 already decided the npm artifact home. 4. **WP-PKG-14 added** — the hosted relay's production rollout had no owning WP anywhere in the program: WP-T3-31 builds the relay and documents its ops story "(Worker + Postgres + cloudflared) … for spec 40/50 consumption" (10-t3code-migration.md:476), and WP-FN-16 FR-7 requires that "its packaging/rollout is carried by specs 40/50" (20-functionality-coverage.md:111) — but the planned inventory carried no deploy-pipeline package for it. WP-PKG-14 (§7) now owns the relay artifact class (§1.3 last row) and its deploy/promotion/rollback pipeline, closing the hand-off both sibling specs already point at.
1.6 Scope deltas recorded for the Gate-B register (WP-RM-03)
Where this spec narrows the surface of a binding decision, the narrowing is recorded here as a labeled scope delta so the Gate-B compliance matrix (WP-RM-03, spec 50) can carry it by ID — the register cites 40-packaging.md §1.6 SD-PKG-<n> and needs no re-derivation or sibling-spec edit:
- **Scope delta SD-PKG-1 — D3 narrowing: on Windows, the environment server runs foreground or desktop-app-managed only in v1; no Windows background service ships.** D3's Form 1 names "service" without OS qualification; this spec ships systemd-user (with lingering) and launchd services (§3.4) but adopts the reference implementation's explicit exclusion of Windows services (
t3:docs/user/background-service.md:90-94). *Rationale*: there is no t3code blueprint to fork for Windows service hosting of a user-context server (service wrapper, session-0 isolation, credential plumbing would all be greenfield), and the desktop app's DesktopBackendManager (WP-T3-26,10-t3code-migration.md:434-440) already provides managed background operation on Windows — so v1 spends the effort where a tested reference exists. *Enforced surface*:environment-server service installon Windows exits with the typed unsupported-platform error naming both alternatives (WP-PKG-05 AC5); the stance is recorded indocs/PACKAGING-DECISIONS.md(WP-PKG-08). *Revisit condition*: a customer requirement for headless unattended Windows hosts triggers an intent-level amendment, which would mint a new work package (Windows service wrapper) rather than stretch WP-PKG-05.
---
2. Form 3 — npm SDK surface (priority 1)
2.1 Exports-map program (publics defined once, under the new names)
- Every one of the 43 publishables (inventory always derived from
scripts/lib/publishable-packages.cjs:122-161, never hand-maintained — FIX-005 discipline, R3 spec input 2) gains anexportsmap that enumerates its public entry points; everything else underdist/becomes unreachable. Today the SDK has **zero** exports entries and everydist/module is deep-importable API (packages/babysitter-sdk/package.json:7-14, R3 §1.6) — D6's re-platforming would otherwise land on an unbounded surface (R3 spec input 8). - Subpath policy: packages expose
"."plus a small named-subpath set (e.g. the orchestrator SDK's storage/journal and breakpoint surfaces that other WPs cite as seams); no"./*"wildcard exports — a wildcard reproduces the unbounded surface with extra steps. - The packed-artifact verifier already checks "root and subpath imports, consumer typecheck and bin smoke tests" per package (
.github/workflows/release-artifact-verifier.yml:5-9, R3 §1.6); WP-PKG-01 turns it from permissive to **exact**: the verifier asserts that the set of importable subpaths equals the exports map, and that deep imports of non-exported paths fail. - Timing is bound by spec 15 §5 rule 6: exports maps + the D10 major bump ride the RN-1 train (WP-RN-05/06/07), so the first
@fabricstack/*publication already carries defined publics.
2.2 Semver posture: lockstep train kept, changesets removed (decision)
**Decision proposed: keep lockstep versioning across the 43-package train; remove the dead changesets machinery; encode breaking-ness at train level, not per package.**
- The lockstep 6.0.x scheme with exact-pinned internal deps and
verify-manifests(scripts/release-version.cjs:24-27; R3 §1.6) is incident-hardened and matches the consumer reality of a **private** product: consumers are the platform itself, the Form 1 archives, and enterprise installs that consume whole trains via dist-tags — not a public ecosystem cherry-picking package versions. Per-package semver would decorate 43 manifests with signals nobody resolves against. - Changesets (
.changeset/config.json:1-20+ one stranded changeset file) are referenced by **no workflow** (R3 §1.1) — R3 spec input 3 requires wire-or-remove; under lockstep-kept, **remove** is the coherent half: delete.changeset/and the pending markdown, so the repo carries one versioning mechanism (scripts/release-version.cjs, the single resolver that is never bypassed — R3 spec input 3). - D10 is expressed at train level: the RN-1/exports cutover publishes as the **7.0.0** train (major bump), and subsequent breaking trains bump the train major. A one-page
docs/VERSIONING.mdrecords the posture: "semver encodes the release train; within-train packages are exact-pinned lockstep; majors mark breaking trains" — plus the alternative considered (per-package semver via changesets) and why it was rejected. - HYG2 closure rides here: wildcard workspace deps (e.g.
"@a5c-ai/atlas": "*"atpackages/atlas/webui/package.json:16, spec 15 §3.2) are eliminated;verify-manifests(already extended by G2(d), spec 15 §4.3) additionally fails on any*/^-ranged internal dependency.
2.3 Private publication and isolation (inherited, verified here)
Publication mechanics are owned by WP-RN-07 (resolver + tags + waves + verifier retargeted to AR, dist-tags main→latest / staging→staging / develop→develop carried over — spec 15 §4.1/§4.2) and guarded by WP-RN-04's G1–G6 (spec 15 §4.3). Form 3's residual obligations, owned by WP-PKG-01/02:
- Publics documentation: the docs-site SDK reference is regenerated from the exports maps (docs QA gates exist,
package.json:116-123, R3 §1.6); the private-install page documents.npmrcscope routing +google-artifactregistry-authper spec 15 §4.1. - No packaging WP creates a public-npm channel for
@fabricstack/*(spec 15 §7 obligation on this spec — restated as binding).
2.4 npm platform packages + launcher (`npx` zero-install, private)
Adopt t3code's pattern: per-platform npm packages holding the release archive's contents **verbatim**, plus a thin launcher listing them as optionalDependencies and exec-ing the one npm installed; publish from tarballs so npm cannot strip the flattened node_modules (t3:scripts/build-npm-platform-packages.ts:2-21,16-21,184, R3 §2.5). npm bytes therefore equal fabric-dist archive bytes.
- Launcher:
@fabricstack/environment(the §3.3 spec 15 unit forapps/environment, binenvironment-server) doubles as the launcher package. - Platform packages: **proposed Gate-B matrix amendment** —
@fabricstack/environment-<platformKey>for platformKey ∈ {darwin-arm64,darwin-x64,linux-x64,linux-arm64,win32-x64,win32-arm64} (the t3code six-target matrix,t3:.github/workflows/release.ymldesktop/cli matrix — R3 §2.4), derived by the rule<unit>-<platformKey>. Flagged for the spec 15 §3 name lock; no WP lands them before that amendment is approved. npx @fabricstack/environmentagainst a private registry requires the consumer's.npmrcscope routing + AR auth; the documented trial flow includes that step (unlike t3code's anonymousnpx t3@latest,t3:docs/user/install.md:38-39— the delta is the price of D19 and is documented, not hidden).
---
3. Form 1 — single-node installer UX (priority 2)
The t3code machinery is the reference implementation (D3; R3 §2 and spec input 6); spec 10 row 12 hands its landing to this spec (10-t3code-migration.md:46), having itself landed only cloud/selfUpdate.ts inside the environment-server tree (WP-T3-19). Everything below is adapted hard-fork material (fork posture per synthesis §2.2 row "install/service/self-update machinery": hard fork, MIT attribution via WP-PKG-12).
3.1 What Form 1 installs
The single-node unit is: **the SEA environment-server executable** (apps/environment, WP-T3-19 — it serves contracts RPC and the local console-web client, per WP-T3-25's "server-served local client" deployment form, 10-t3code-migration.md:427), **the SEA orchestrator CLI** (metapackage bin, spec 15 §3.1 row 1), **console-web static assets**, and the native prebuilds the server needs (node-pty; optional resource-monitor sidecar — 10-t3code-migration.md:380). Mobile shells are out (D13). The desktop app (@fabricstack/console-desktop, WP-T3-26) is a sibling distribution riding the same pipeline and fabric-dist channel (§3.7), not part of the headless archive.
3.2 Archives (adapting `build-cli-archive.ts`)
- One self-contained archive per platform/arch, named
environment-<version>-<platform>-<arch>.tar.gz(zip on Windows), containing the two SEA executables, console-web assets, and production-installed runtime-external native packages — the "no Node, npm, or compiler on the unpacking machine" contract (t3:scripts/build-cli-archive.ts:1-16). SHA256SUMSpublished beside the archives (consumed by the installers exactly ast3:scripts/install.sh:194-215does). Detached trust-core signatures overSHA256SUMSare **deferred, with rationale**:fabric-distdownloads are already authenticated + TLS (unlike t3code's anonymous GitHub CDN), so checksums cover corruption and signatures add value only for offline mirrors — recorded as a follow-on in the WP-PKG-03 closing note, not built now.- Versions come from the single resolver (
scripts/release-version.cjs— R3 spec input 3): archive versions are the same train versions npm gets; commit-pinned prereleases (<base>-<channel>.<short-sha>,scripts/lib/release-version.cjs:146-151) name staging/develop archives.
3.3 Installers (adapting `install.sh` / `install.ps1`)
- POSIX installer adapted from the 227-line dependency-free
t3:scripts/install.sh:1-4("Needs only sh, tar, sha256sum or shasum, and curl or wget") andt3:scripts/install.ps1:7-22for Windows, kept dependency-free. - Env-var surface uses the platform-wide
FABRIC_prefix (spec 15 §2/§3.4):FABRIC_CHANNEL(=stable|staging|dev, §5),FABRIC_VERSION(exact pin),FABRIC_DIST_BASE_URL(mirror override),FABRIC_DIST_TOKEN(auth),FABRIC_HOME(layout root, default~/.fabric) — mirroringT3CODE_CHANNEL/T3CODE_VERSION/T3CODE_RELEASE_BASE_URL(t3:scripts/install.sh:8-16). No new prefix is minted. - **Private-download auth (the one structural delta from t3code)**: the installer resolves credentials in order —
FABRIC_DIST_TOKENenv; elsegcloud auth print-access-tokenwhen gcloud ADC is present; else fail with a documented pointer to the private-install doc. Downloads hitfabric-distover authenticated HTTPS. There is no anonymous path — that is D19 working as intended. - Channel/version resolution keeps t3code's safety rails: per-train version listing so a stable install can never pick a prerelease (
t3:scripts/install.sh:145-160), un-requested trains refused (install.sh:163-174), targeted error on missing checksum asset (install.sh:196). - Versioned layout shared with the service: archives unpack to
$FABRIC_HOME/runtime/versions/<version>(t3code's layout,install.sh:17-19,177, with~/.t3→~/.fabricper spec 15 §3.5); staging dir + smoke-run (environment-server --version) +.install-completemarker + atomic move (install.sh:194-215); user entry point symlinked~/.local/bin/environment-server → versions/<v>/environment-serverwith a PATH hint (install.sh:220-221,223-226). Progress UX on stderr survives piping (install.sh:33-52,88-118). - **First-boot key provisioning (the WP-FN-02/WP-FN-03 hand-off, made concrete here)**: the install finishes by invoking the installed binary's idempotent
environment-server keys init, which generates — only when absent — the default **run signing key** (WP-FN-02 AC4: "spec 40 P-04 provisions a default run key so the shipped platform signs by default") and the **attestation keys** (proxy attestation key + model-decision signing key) that make WP-FN-03 AC2's default-covered posture real ("with keys present (the packaged default), proxy attestation and in-process model-decision signing are ON without env opt-ins"). Key material lands in$FABRIC_HOME/keys/(0700 dir, 0600 files) — a data directory per spec 15 §3.5, deliberately **outside**runtime/versions/<v>so keys survive updates (WP-PKG-06) and uninstall (WP-PKG-08 AC1 preserves data directories). Owned by WP-PKG-04 (generation) and WP-PKG-05 (service-start refusal when keys are missing). - Installer entry:
curl -fsSL <install-url> | sh/irm <install-url> | iexwhere<install-url>is a fabric-operated HTTPS location serving the scripts (the scripts themselves are not secret; only artifact downloads are authenticated). The t3code precedent ishttps://t3.codes/install.sh(t3:docs/user/install.md:13-21). - Installer tests ride the t3code precedent of a real test file (
t3:scripts/install.test.ts, R3 §2.1).
3.4 Background service
- Adopt t3code's backends: **systemd user services with lingering** on Linux, **launchd LaunchAgent** on macOS (
t3:docs/user/background-service.md:45-54);service installis a **reconciler** (install/update/repair from the CLI's own version, downgrade refused without--allow-downgrade—t3:apps/server/src/cli/service.ts:38-63);service statusdiagnoses concrete problem codes (linger-disabled,user-manager-unavailable,service-stopped,restart-pending) with repair guidance (background-service.md:83-88). - **Windows-service stance (decision — recorded as scope delta SD-PKG-1, §1.6)**: Windows background services are **out of scope**, adopting the reference's explicit position (
t3:docs/user/background-service.md:90-94). On Windows the installer installs the binaries and the recommended managed path is the desktop app, whose DesktopBackendManager owns spawning the bundled environment server (WP-T3-26,10-t3code-migration.md:434-440).environment-server service installon Windows exits with a typed unsupported-platform message pointing at both alternatives (foreground run; desktop app). This narrows D3's "service" for one OS; the rationale and revisit condition live in the SD-PKG-1 entry (§1.6), which WP-RM-03's Gate-B register carries, in addition to the WP-PKG-05 doc page. - Command surface hangs off the installed binary, mirroring t3's single-binary subcommands (
t3 service install|status|restart|uninstall,t3 update,t3 uninstall—t3:docs/user/install.md:28-35):environment-server service …,environment-server update,environment-server uninstall.
3.5 Self-update
- Adopt the launcher-owned two-phase architecture: a **stable launcher** (from
t3:apps/server/src/serviceLauncher.ts) is the runtime systemd/launchd selects and the only writer of durable service state; server children request updates over inherited IPC and never rewrite their own service definition (t3:docs/internals/server-updates.md:3-8). - Two-phase commit verbatim: pending update durably recorded → target starts as a **trial** (migrations done, HTTP bound, parked at the activation gate) →
prepared→ durable commit → live; failed/timed-out trials roll back (server-updates.md:17-31). **SQLite snapshot rollback**: launcher snapshots main/WAL/shm after stopping the old child so trial migrations are reversible without down-migrations, with a durable restore marker (server-updates.md:33-43). - Exact-version installs keep restarts independent of cache eviction and moving tags (
server-updates.md:10-14); capability negotiation distinguishesdesktop-managedvsboot-serviceself-update (t3:apps/server/src/cloud/selfUpdate.ts:40-47— this module lands with WP-T3-19 per spec 10 row 12 and is *activated* here). environment-server updatesemantics pert3:docs/user/background-service.md:23-36: newest release on the channel, prompt before restarting live work, exact-version pinning,--channeltrain switching,--allow-downgradeguard. Update downloads hitfabric-distwith the §3.3 auth chain.
3.6 Entry-point naming (Gate-B note)
The installed entry point is the environment-server bin — the spec 15 §3.3 name for apps/environment; this spec does not mint a new bin. A short launcher alias (candidate: fabric) would improve CLI ergonomics (environment-server service install is a mouthful next to t3 service install) but is a **new binary name requiring a spec 15 §3 matrix amendment at the Gate-B name lock**; it is proposed there, and every WP below is written to work without it.
3.7 Desktop channel note
@fabricstack/console-desktop (WP-T3-26) ships as electron-builder artifacts published to fabric-dist under desktop/, with merged mac/win auto-update manifests generated at release time per the t3code pattern (t3:scripts/merge-update-manifests.ts:19-24; desktop's token-based two-phase update handoff per t3:docs/internals/server-updates.md:52-57). Desktop artifact production is an acceptance item of WP-PKG-03/06, not a separate WP: the reference implementation builds desktop targets inside the same release workflow (six-target matrix, t3:.github/workflows/release.yml — R3 §2.4), and splitting it would duplicate the pipeline.
---
4. Form 2 — cluster/Helm bundle (priority 3)
4.1 One versioned platform chart
- The chart un-freezes:
version/appVersionmove from the static0.1.0(packages/kradle/charts/Chart.yaml:1-7) to the **platform train version** resolved byscripts/release-version.cjs, stamped at package time (never committed as a moving value). Chart name and K8s identities follow spec 15 §3.6 (control-plane, CRD groupcontrol-plane.fabric.internal, namespacecontrol-plane-system) — and per spec 15 WP-RN-12's dependency note, **re-versioning and renaming land as one chart-consumer break**: WP-PKG-09 and WP-RN-12 ship in the same train. - Container image references in
values.yamldefault to platform-version tags in the private AR Docker repo (today images are only sha-tagged at deploy time,publish.yml:812-816); the sha tag remains as a co-tag for provenance.
4.2 Fold the three deploy mechanisms into the bundle
Today "the platform is not one bundle" (R3 §1.2): kradle chart + atlas-webui Kustomize overlay (publish.yml:740-742) + Envoy AI Gateway Kustomize (Chart.yaml:31-34), with CRDs applied out-of-band (publish.yml:955). Fold decisions:
| Mechanism | Disposition | Why |
|---|---|---|
| atlas-webui Kustomize overlay | **Folded into the chart** as catalog-webui templates behind an install condition (like the existing vela/kyverno/jitsi conditions, Chart.yaml:17-35) | It is fabric's own service; envsubst-piped Kustomize (publish.yml:740-742) exists only because nobody folded it — no upstream constraint |
| Envoy AI Gateway Kustomize | **Stays out of the chart; becomes an operator-managed install phase** (operator install renders/applies it, replacing the CI-only step) | Upstream deliberately deploys Envoy via Kustomize, not as a subchart (Chart.yaml:31-34) — the deliberate choice is respected; the *customer-facing* unit becomes one operator command anyway |
CRDs (8 files, packages/kradle/charts/crds/) | Chart keeps crds/ for first install; the **operator owns upgrades** via server-side apply, productizing what CI does today (kubectl apply -f … --server-side --force-conflicts, publish.yml:955) | Helm does not upgrade crds/ contents; today's out-of-band step is correct and just needs an owner that is not the company CD |
Vendored subcharts (jitsi-meet 1.5.2, kyverno 3.9.1, vela-core 1.10.9 — packages/kradle/charts/charts/) | Remain vendored in the published chart | Private distribution must not depend on third-party chart repos at install time |
4.3 OCI publication and the operator as the entry point
- Chart published to
oci://us-central1-docker.pkg.dev/boot-464019/fabric-chartsper train, with channel co-tags mirroring the dist-tag scheme (§5). The publish step lives in fabric's private pipeline (spec 15 §4.2), gated by the same packed/verify discipline as npm (chart render + install dry-run gate in the verifier lane). @fabricstack/operator(the renamed kradle-installer, spec 15 §3.1 row 43; today@a5c-ai/kradle-installer, binkradle-install, targetsminikube|existing|eks|aks|gke, Terraform GKE renderer —packages/kradle/installer/src/types.ts:3,SPEC.md:1-33,src/terraform/root.ts:115-193) becomes the operator entry consuming **published** artifacts: it pulls the chart by OCI ref + version instead of rendering against repo paths, and drops the CI-flag coupling (A5C_CLOUD_*→FABRIC_*repo variables per spec 15 §3.4; deploy hooks currently gated atpublish.yml:1316-1321).- Dogfooding closes the loop: the company's own
deploy_kradlejob (publish.yml:792-1130) switches fromhelm upgrade --install kradle packages/kradle/charts(publish.yml:1080) to installing the **published** OCI chart at the resolved train version — CI becomes the first customer of the bundle, so a broken publication breaks CD before it breaks a customer.
---
5. Channels and trains (cross-form; adapting t3code's discipline to fabric's branch model)
**Decision proposed: keep fabric's three branch-mapped channels, present them installer-facing as stable/staging/dev, and adopt t3code's exact-commit promotion discipline; no cron-driven nightly train is added.**
- Mapping:
stable↔ branchmain↔ dist-taglatest;staging↔staging↔staging;dev↔develop↔develop(scripts/lib/release-version.cjs:44-46). The same three channel names select archives infabric-dist, desktop manifests, and chart co-tags — one channel vocabulary across all three forms, all resolved by the single resolver (R3 spec input 3; sha-suffixed prerelease comparison rules preserved,release-version.cjs:162-170). - **Exact-commit promotion**: t3code's core discipline — "stable only ever ships a build that nightly users have already run" (
t3:.github/workflows/release.yml:44-47,88-93) — is adopted as: astablerelease must promote the **exact commit** of a previously publishedstagingrelease; the release job verifies this against thefabric/staging/v*tag set (tags per spec 15 §4.2) and refuses otherwise. Commit-pinned prerelease versions (<base>-staging.<short-sha>) make the check mechanical. - **No nightly cron**: t3code's 30-minute nightly cron (
release.yml:4-27) exists to give an anonymous public user base a fresh train. Fabric'sdevelopchannel already publishes per-merge commit-pinned prereleases — a push-driven "nightly" with better provenance — and the private audience does not need cadence-based freshness. Adding a fourth train would complicate the resolver for zero consumers. t3code's concurrency separation (nightly never blocks stable,release.yml:29-37) is still adopted:dev-channel publication runs in a separate concurrency group fromstaging/stable. - Preview-train safety rail equivalent: un-requested channels are refused at install/update time (§3.3;
t3:scripts/install.sh:163-174).
---
6. CI/publish isolation (cross-form)
Spec 15 §4.3's G1–G6 cover npm. This spec extends isolation to every artifact class it creates (owned by WP-PKG-13; WP-PKG-14 AC5 further extends PG1 to the relay's deploy targets):
| Guard | Mechanism |
|---|---|
| **PG1 — no public artifact endpoints** | The WP-RN-04 CI enforcement job gains assertions: no workflow file references ghcr.io, registry.npmjs.org, public GitHub releases upload actions, or non-pkg.dev helm/oci push targets in a publish context. Negative fixtures included (a deliberately-bad workflow snippet must trip it) |
| **PG2 — inert-workflow removal** | The nested non-executing workflow carrying the ghcr helm push (packages/kradle/core/.github/workflows/publish.yml:94, R3 inferred finding 1) is deleted, so it can never be promoted to the root by a refactor |
| **PG3 — installer URL pinning** | install.sh/install.ps1 default FABRIC_DIST_BASE_URL to the fabric-dist AR endpoint; CI asserts the shipped scripts contain no github.com/.../releases or npmjs URL |
| **PG4 — public-repo non-interference** | Restated invariant: no WP in this spec touches a5c-ai/babysitter names, publishing, tags, or workflows (intent acceptance criterion 7; enforced by WP-RN-02's guard). The public repo's npm-global install story for @a5c-ai/* (installation.md:126-131) continues to exist **there**, untouched |
| **PG5 — credential starvation extended** | Fabric repo secrets contain no ghcr/npmjs/public-release credentials; CI asserts no workflow consumes such a secret (extends G3, spec 15 §4.3) |
---
7. Work packages
Fourteen work packages: the Gate-A planned inventory (synthesis §4.4) with the three §1.5 scope adjustments, plus WP-PKG-14 (§1.5 delta item 4) owning the relay production rollout, which previously had no owner among the program's work packages.
WP-PKG-01: Exports-map program — publics defined across the renamed 43-package matrix
- **Objective**: Every publishable package declares its public API via an
exportsmap (root + enumerated subpaths, no wildcards); deep imports of non-exported modules fail; the packed-artifact verifier enforces exactness; publics ship once, under@fabricstack/*names, in the D10 major-bump train (7.0.0) together with RN-1. - **Affected paths**: all 43 manifests enumerated by
scripts/lib/publishable-packages.cjs:122-161(exports fields; todaypackages/babysitter-sdk/package.json:7-14has zero exports entries andpackages/adapters/sdk/package.jsonexports only "." — R3 §1.6);.github/workflows/release-artifact-verifier.yml(exactness assertions added to the FIX-011 gate,release-artifact-verifier.yml:1-16); source import sites that deep-import now-private paths (workspace-wide sweep); docs-site SDK reference pages regenerated from exports maps (package.json:116-123docs gates). - **Acceptance criteria**: (1)
node -e "require('@fabricstack/orchestrator-sdk/dist/internal-anything')"-style non-exported deep imports fail for every package (verifier negative matrix, at least one probe per package); (2) all declared subpaths import and typecheck in the verifier's fresh-install consumer lane (release-artifact-verifier.yml:5-9extended); (3) zero in-repo imports of non-exported paths (CI grep/resolver check); (4) the exports cutover and the RN-1 rename publish as one train versioned 7.0.0 (tagfabric/<branch>/v7.0.0exists; no intermediate train carries renamed-but-unbounded packages); (5) docs-site publics reference lists exactly the exported subpaths per package. - **Test requirements**: packed-artifact verifier full 43-package matrix green with the exactness assertions; consumer-typecheck lane green; WP-TEST-09 (Q-09) projection/parity suites compile against the declared publics only.
- **Dependencies**: WP-RN-05, WP-RN-06 (same train; collision-free bin set), WP-RN-07 (train publishes to AR); blocks the public-surface claims of WP-T3-05 consumers.
- **Size**: L
WP-PKG-02: Versioning discipline — lockstep kept, changesets removed, HYG2 closed
- **Objective**: Execute the §2.2 decision: one versioning mechanism (the single resolver), changesets deleted, train-level semver posture documented, wildcard internal deps eliminated.
- **Affected paths**:
.changeset/config.json:1-20+.changeset/observer-dashboard-replacement.md(deleted); newdocs/VERSIONING.md(posture + rejected alternative recorded);scripts/release-version.cjs:24-27verify-manifests(new failure on*/range-versioned internal deps);packages/atlas/webui/package.json:16and every other wildcard internal dep found by the check. - **Acceptance criteria**: (1) repo-wide grep for
changesetin.github/workflowsand tree returns zero live references; (2)verify-manifestsfails on a fixture manifest with"@fabricstack/catalog": "*"and passes on the cleaned tree; (3)docs/VERSIONING.mdstates the lockstep-train posture, the D10 major-bump rule, and the changesets-removal rationale; (4) the 7.0.0 train publishes with all internal deps exact-pinned (existing verify + new check both green). - **Test requirements**:
verify-manifestsnegative fixture in CI; release train dry-run (release-version.cjs resolve/verify) green. - **Dependencies**: WP-RN-07 (resolver retarget lands first so this extends the retargeted script once).
- **Size**: M
WP-PKG-03: SEA executables + self-contained per-platform archives + private dist publication
- **Objective**: Build
environment-serverandorchestratoras Node SEA executables; produce one self-contained archive per platform/arch (§3.2 contents, six-target matrix) plusSHA256SUMS; publish per train to thefabric-distAR generic repo; desktop electron-builder artifacts + merged update manifests ride the same pipeline (§3.7). - **Affected paths**: new
scripts/build-archives/(adapted hard-fork oft3:scripts/build-cli-archive.ts:1-16andt3:scripts/merge-update-manifests.ts:19-24; attribution per WP-PKG-12); fabric's private publish workflow (spec 15 §4.2publish-npm-private.ymlgains archive build/publish jobs with the §5 concurrency split);apps/environment+ orchestrator CLI SEA entry configs; CI Node matrix (SEA build hosts ≥25.7 — §1.4);fabric-distrepo provisioning inboot-464019(WIF SAroles/artifactregistry.writer, mirroring spec 15 §4.1's npm setup). - **Acceptance criteria**: (1) on a clean VM per platform (no Node, no npm, no compiler), unpacking the archive and running
environment-server --versionandorchestrator --versionsucceeds — thebuild-cli-archivecontract holds; (2)SHA256SUMSverifies for every asset; a corrupted archive fails installer verification (negative test); (3) archives exist infabric-distfor a full staging train at the resolver-derived version, addressable by channel and by exact version; (4) console-web is served by the unpackedenvironment-serverand loads in a browser (smoke); (5) desktop artifacts + merged mac/win update manifests published underdesktop/for the same train; (6) build jobs run on Node ≥25.7 hosts and the workspace engines floor (≥22.16) is untouched (CI matrix asserts both); (7) zero public-endpoint references in the new jobs (PG1 green). - **Test requirements**: per-platform boot smoke in the release pipeline (t3code's
release-smoke.tspattern, R3 §2.4); checksum negative test; WP-TEST-04 (Q-04) consumes these archives as its packaged-install input. - **Dependencies**: WP-T3-19 (environment server exists), WP-T3-32 stage 2 (toolchain + floors), WP-T3-25 (console-web assets), WP-PKG-13 (
fabric-distguards live), WP-RN-07 (resolver/train); WP-T3-26 for the desktop artifacts. Unblocks WP-T3-28's bootstrap-download activation (10-t3code-migration.md:451-453). - **Size**: L
WP-PKG-04: `install.sh` / `install.ps1` — channel-aware, checksum-verified, authenticated, versioned layout
- **Objective**: Ship the §3.3 installers: dependency-free POSIX + PowerShell scripts installing from
fabric-distinto$FABRIC_HOME/runtime/versions/<v>with symlinked entry point, channel/version/mirror/auth env-var surface (FABRIC_CHANNEL|VERSION|DIST_BASE_URL|DIST_TOKEN|HOME), t3code's safety rails and UX — and first-boot provisioning of the default run signing key and attestation keys (§3.3), so the installed platform satisfies WP-FN-02 AC4 and WP-FN-03 AC2 (spec 20) out of the box: signing and attested by default, no opt-in. - **Affected paths**: new
scripts/install/install.sh+scripts/install/install.ps1(adapted hard-forks oft3:scripts/install.sh(227 lines) /t3:scripts/install.ps1(224 lines)); newscripts/install/install.test.ts(pert3:scripts/install.test.tsprecedent);apps/environmentkeys initCLI surface (the idempotent generator the installers invoke, §3.3 — key formats per WP-FN-02's trust-core run-key configuration inpackages/babysitter-sdk/src/config/and WP-FN-03's proxy/model attestation key seams, spec 20); hosting of the scripts at the fabric-operated install URL;docs/user-guide/getting-started/installation.mdprivate-install rewrite (coordinated with WP-RN-13, which owns the docs sweep — spec 15 WP-RN-13 affected paths already nameinstallation.md:126-131). - **Acceptance criteria**: (1) on clean Linux/macOS/Windows machines with only a
FABRIC_DIST_TOKEN, the one-line install produces a workingenvironment-server --versionvia the symlink, with layout exactly$FABRIC_HOME/runtime/versions/<v>+.install-completemarker; (2) checksum verification: a tampered archive aborts before the atomic move (negative test); (3) channel rails:FABRIC_CHANNEL=stablecan never resolve a-staging./-develop.prerelease; an unrequested channel is refused (fixture tests mirroringinstall.sh:145-174behavior); (4)FABRIC_VERSIONexact-pin installs that version;FABRIC_DIST_BASE_URLmirror override honored; (5) auth chain order (token → gcloud ADC → documented failure) proven by matrix test; (6) re-running the installer over an existing install is idempotent and atomic (staging-dir + move semantics); (7) PATH hint printed when~/.local/binis not on PATH; (8) **default key provisioning (WP-FN-02 AC4 / WP-FN-03 AC2, spec 20)**: a fresh install generates the run signing key and the attestation keys (proxy attestation + model-decision signing) into$FABRIC_HOME/keys/with 0700/0600 permissions via the idempotentkeys initstep (§3.3); the first run on a fresh install produces a trust-core-signed journal (WP-FN-02 AC1 envelope present) and journals **neither** thejournal-signing-disablednor theattestation-disabledloud marker (WP-FN-02 AC3 / WP-FN-03 AC1) — proving the shipped default is the covered posture; re-running the installer over an existing$FABRIC_HOMEnever regenerates or overwrites existing keys (idempotence fixture asserts byte-identical key files). - **Test requirements**: installer test suite in CI against a staged
fabric-distfixture (token-authenticated local mock acceptable for PR CI; real AR in the release lane); key-provisioning assertions in that suite (fresh vs pre-populated$FABRIC_HOME, permission bits, signed-first-run smoke); WP-TEST-04 uses this installer as step 1 of the whole-platform e2e and asserts the signed-by-default posture. - **Dependencies**: WP-PKG-03 (artifacts exist), WP-PKG-11 (channel vocabulary final); WP-FN-02/WP-FN-03 (spec 20 — they define the key formats, consumers, and posture semantics this provisioning must satisfy; their ACs cross-reference this WP as "spec 40 P-04").
- **Size**: M
WP-PKG-05: Background service — systemd-user + linger, launchd; reconciling `service install`; Windows out of service scope
- **Objective**:
environment-server service install|status|restart|uninstallper §3.4: reconciler semantics with downgrade guard, diagnostic problem codes with repair guidance, launchd LaunchAgent + systemd user unit with lingering; Windows returns the typed unsupported-platform guidance (desktop app or foreground — scope delta SD-PKG-1, §1.6); service start refuses to run key-less by default, guarding the WP-FN-02/WP-FN-03 covered posture (§3.3 keys). - **Affected paths**:
apps/environment/src/cli/service.ts+ platform backends (adapted fromt3:apps/server/src/cli/service.ts:38-63and the backend split described int3:docs/user/background-service.md:45-54); service unit/plist templates (fabric identifiers, e.g. LaunchAgent label under a neutral reverse-DNS consistent with spec 15 §3.6's.internalposture);docs/user-guide/background-service page (incl. the Windows stance record, §3.4). - **Acceptance criteria**: (1) on Linux:
service installenables lingering, installs the user unit, starts it, andservice statusreports healthy; disabling linger yields problem codelinger-disabledwith repair text (fault-injection test); (2) on macOS: LaunchAgent installed/loaded, survives logout-login (the linger-equivalent check); (3) reconciler proven:service installfrom an older binary refuses (downgrade guard) unless--allow-downgrade; from a newer binary it updates the service in place (service.ts:38-63semantics); (4)service uninstallremoves unit/agent and leaves user data intact; (5) on Windows:service installexits non-zero with the typed unsupported message naming the two alternatives (test); (6) the service runs the versioned layout's launcher path, not a bare copy (layout integration with WP-PKG-04); (7) **key-presence refusal (WP-FN-02 AC4 / WP-FN-03 AC2, spec 20)**: on service start, missing or unreadable$FABRIC_HOME/keys/material (the WP-PKG-04 AC8 provisioned set) is a **refusal** — the service exits before binding,service statusreports the new problem codekeys-missingwith repair guidance (environment-server keys init, or re-run the installer), and the service never silently starts in the unsigned/unattested posture (fault-injection test: delete the keys dir, assert refusal + problem code). The only escape is the operator's *explicit* unsigned/unattested configuration, in which case start proceeds and the run journals the loudjournal-signing-disabled/attestation-disabledmarkers exactly per WP-FN-02 AC3 / WP-FN-03 AC1 (opt-out path test asserts the markers) — the refusal guards the shipped default; it does not remove the documented explicit posture. - **Test requirements**: per-OS service lifecycle suite in the release lane (containerized systemd for Linux; macOS runner for launchd); problem-code fault-injection matrix per
background-service.md:83-88codes plus the fabric-addedkeys-missingcode (AC7); explicit-opt-out marker assertion shared with WP-FN-02/03's posture matrix (spec 20). - **Dependencies**: WP-PKG-03, WP-PKG-04 (key provisioning it refuses without); WP-T3-19 (the server being serviced); WP-FN-02/WP-FN-03 (spec 20 — posture semantics and loud-marker contracts).
- **Size**: M
WP-PKG-06: Self-update — launcher-owned two-phase commit with SQLite snapshot rollback
- **Objective**: Implement §3.5: stable launcher owning durable service state;
environment-server updatewith channel switching, exact pinning, restart consent, downgrade guard; trial-activate-commit update flow with SQLite main/WAL/shm snapshot rollback; desktop-managed vs boot-service capability negotiation; desktop auto-update manifests consumed by console-desktop. - **Affected paths**: new
apps/environment/src/launcher/(adapted hard-fork oft3:apps/server/src/serviceLauncher.ts); update orchestration activating thecloud/selfUpdate.tsmodule landed by WP-T3-19 (t3:apps/server/src/cloud/selfUpdate.ts:40-47; spec 10 row 12,10-t3code-migration.md:46);updateCLI command; snapshot/restore machinery beside the environment's SQLite store (10-t3code-migration.md:380-381— the WP-T3-19 event store); console-desktop update consumption of the §3.7 manifests. - **Acceptance criteria**: (1) happy path:
updateon a service install downloads the exact target version fromfabric-dist, trial-starts it (migrations complete, HTTP bound, parked at the activation gate), commits durably, goes live; the old version remains on disk in the versioned layout; (2) rollback: a trial that fails migrations or times out rolls back automatically, and the SQLite main/WAL/shm snapshot restores byte-identical pre-trial state (checksummed fixture test perserver-updates.md:33-43); a restore marker survives a simulated crash mid-restore; (3) the launcher is the only writer of service definitions: a child attempting to rewrite service state is rejected over IPC (unit test of theserver-updates.md:3-8invariant); (4) restart consent: with a live run in progress,updateprompts (or defers under--no-restart) before restarting (background-service.md:23-36semantics); (5) downgrade refused without--allow-downgrade; channel switchupdate --channel stagingmoves the install to the staging train; (6) desktop capability: a desktop-managed environment reportsdesktop-managedand defers restart authority to the desktop app (capability-negotiation test perselfUpdate.ts:40-47). - **Test requirements**: update state-machine unit suite (all trial outcomes); crash-injection restore test; end-to-end update between two real published staging train versions in the release lane.
- **Dependencies**: WP-PKG-03, WP-PKG-05; WP-T3-19 (selfUpdate module + SQLite store); WP-T3-26 for the desktop-managed path.
- **Size**: L
WP-PKG-07: npm platform packages + launcher — npm bytes equal archive bytes (private)
- **Objective**: Per §2.4:
@fabricstack/environmentbecomes the thin launcher with per-platformoptionalDependenciespackages holding the archive contents verbatim, published from tarballs to AR;npx @fabricstack/environmentis the documented zero-install trial for authenticated consumers. - **Affected paths**: new
scripts/build-npm-platform-packages/(adapted hard-fork oft3:scripts/build-npm-platform-packages.ts:2-21, tarball publication per:16-21, exec dispatch per:184);apps/environment/package.json(launcher shape: binenvironment-server, optionalDependencies on the six platform packages); the private publish workflow (platform packages join the wave publication after the archive build); spec 15 §3 matrix amendment record for the@fabricstack/environment-<platformKey>names (Gate-B item, §2.4 — this WP does not start until the amendment is locked). - **Acceptance criteria**: (1) byte-equality: the files inside
@fabricstack/environment-linux-x64match thelinux-x64archive of the same train (checksum comparison in the release lane); (2) on a machine with Node ≥22.16, configured.npmrcscope routing and AR auth,npx @fabricstack/environment --version(or the documented two-step equivalent) runs the platform binary;npm i -g @fabricstack/environmentinstalls the workingenvironment-serverbin; (3) publication uses tarballs so the flattenednode_modulessurvives npm (verifier unpacks and asserts); (4) the launcher exits with a targeted error naming supported platforms when no platform package matched; (5) all six platform packages carry G1publishConfig.registrypinning and pass G6's scope check (spec 15 §4.3); the publishable-inventory selector counts them (selector update per G1's criterion,scripts/lib/publishable-packages.cjs:136-137). - **Test requirements**: byte-equality gate in the release lane; published-consumer validation job pattern (
publish-packages-from-tag.yml:20-27) extended to the launcher; per-platform npx smoke on the CI matrix. - **Dependencies**: WP-PKG-03 (archives are the byte source), WP-RN-03/04/07 (registry, guards, train); Gate-B name-lock amendment for platform-package names.
- **Size**: M
WP-PKG-08: Uninstall story + no-public-channel decision record
- **Objective**:
environment-server uninstallremoves service, launcher symlink, and downloaded versions while keeping user data (~/.fabricrun/journal state preserved), mirroringt3 uninstall(t3:docs/user/background-service.md:38-41); the §1.5 decision dropping public OS package managers (winget/brew/AUR/deb — the t3code matrix att3:docs/user/install.md:61-67and AUR automationt3:packaging/aur/t3code-bin/PKGBUILD:3-9+t3:.github/workflows/publish-aur.yml:5-17are **not** adapted) is recorded as a packaging decision with its D19 rationale. - **Affected paths**:
apps/environment/src/cli/uninstall.ts; installer docs page ("Uninstalling" section);docs/PACKAGING-DECISIONS.md(new: the no-public-channel record, the §1.3 artifact-home table incl. the relay row, the §3.4 Windows stance recorded verbatim as scope delta SD-PKG-1 (§1.6), and the §3.2 signature deferral — the single place packaging decisions live). - **Acceptance criteria**: (1) after
uninstallon a machine with a service install: no service unit/agent remains, no symlink,$FABRIC_HOME/runtime/removed,$FABRIC_HOME's data directories (runs/journal/profiles per spec 15 §3.5, andkeys/per WP-PKG-04 AC8 — a reinstall must sign with the same identity) intact — asserted by fixture comparison; (2)uninstallis safe to run twice (idempotent); (3) the decision record exists, states that no@fabricstack/*artifact ships via winget/brew/AUR/deb/public GitHub Releases/ghcr/npmjs, carries the SD-PKG-1 scope-delta entry (§1.6) with its rationale and revisit condition, and is cross-linked from the Gate-B compliance matrix (WP-R-03 in spec 50's planned inventory); (4) repo-wide grep confirms no AUR/winget/brew publication automation was introduced. - **Test requirements**: uninstall lifecycle test in the per-OS service suite (extends WP-PKG-05's lane); docs-quality gate on the decision record.
- **Dependencies**: WP-PKG-04, WP-PKG-05, WP-PKG-06.
- **Size**: S
WP-PKG-09: Helm chart productization — version-with-platform, OCI publication, one bundle
- **Objective**: Execute §4.1–4.2: chart re-versioned per train (unfreezing
0.1.0,packages/kradle/charts/Chart.yaml:1-7), renamed with WP-RN-12 in the same consumer break, atlas-webui overlay folded in as conditionalcatalog-webuitemplates, image references platform-version-tagged, chart published per train tooci://us-central1-docker.pkg.dev/boot-464019/fabric-chartswith channel co-tags, render/install gates in the verifier lane. - **Affected paths**:
packages/kradle/charts/(post-RN-10 home per spec 15 WP-RN-12;Chart.yaml,values.yaml(617 lines), newtemplates/catalog-webui/*replacing the Kustomize overlay consumed atpublish.yml:740-742); the private publish workflow (chart package/push job, version stamped fromscripts/release-version.cjs);fabric-chartsAR repo provisioning; image tag co-tagging in the build jobs (publish.yml:812-816,880-893); deletion of the inert nested workflow (packages/kradle/core/.github/workflows/publish.yml, PG2). - **Acceptance criteria**: (1) a staging train publishes chart
control-planeat the train version to AR OCI;helm show chart oci://…/control-plane --version <v>returns matchingversion/appVersion; (2)helm installof the published chart (no source checkout) on a clean minikube brings up the platform including catalog-webui when enabled — replacing the source-path flow documented atpackages/kradle/charts/README.md:30-56; (3) the atlas-webui Kustomize overlay step is deleted from CI, with catalog-webui served from the chart in all three GKE environments; (4) default image references resolve to platform-version tags in the private AR Docker repo (rendered-values golden test); (5) chart render + install dry-run gate wired into the verifier lane and red on a template error (negative fixture); (6) vendored subcharts still install with no external chart-repo fetch (air-gap render test); (7) lands in the same train as WP-RN-12 (one chart-consumer break — tag audit). - **Test requirements**: chart render golden tests; clean-minikube install e2e; three-environment GKE deploy green post-cutover (
publish.yml:1131smoke per environment). - **Dependencies**: WP-RN-07 (train), WP-RN-12 (same-train rename partner), WP-PKG-13 (
fabric-charts+ PG guards), WP-PKG-11 (channel co-tags). - **Size**: L
WP-PKG-10: Operator alignment — consume published artifacts, decouple from CI flags and repo paths
- **Objective**:
@fabricstack/operator(spec 15 §3.1 row 43) installs/upgrades from the **published** OCI chart by version/channel, owns CRD upgrade applies (productizingpublish.yml:955) and the Envoy install phase (§4.2), keeps its Terraform/target surface (minikube|existing|eks|aks|gke,packages/kradle/installer/src/types.ts:3; GKE modulesrc/terraform/root.ts:115-193), and drops CI-variable coupling; the company's own CD becomes its first consumer. - **Affected paths**:
packages/kradle/installer/(post-RN homepackages/control-plane/operator): chart source resolution switched from repo path (buildKradleHelmPlan/renderHelmValuesYaml,src/index.ts:4-6) to OCI ref + resolver-derived version; new CRD-lifecycle phase (server-side apply of the published chart'scrds/); new Envoy phase (renders/applies the gateway Kustomize the chart deliberately excludes,Chart.yaml:31-34); config surface renamedA5C_CLOUD_*→FABRIC_*(spec 15 §3.4; today's gating atpublish.yml:1285-1357,1316-1321);deploy_kradle(publish.yml:792-1130) retargeted to runoperator install --env <env>against the published chart (dogfooding, §4.3). - **Acceptance criteria**: (1) on a clean workstation with only AR reader credentials and the operator installed from AR npm,
operator install --target minikubedeploys the full bundle — chart + CRDs + Envoy — with zero fabric source checkout; (2)operator upgradeagainst a cluster running train N installs train N+1 including CRD changes (server-side apply proven by a CRD-schema-change fixture between two staging trains); (3) grep: zero repo-path chart references and zeroA5C_CLOUD_*reads remain in the operator; (4) CI's three GKE environments deploy via the operator consuming published artifacts (pipeline diff showshelm upgrade --install … packages/kradle/chartsatpublish.yml:1080replaced); (5)operator statusreports chart version, app version, and channel for a deployed environment. - **Test requirements**: operator install e2e on minikube in CI; upgrade-with-CRD-change fixture test; deployed-stack smoke (
publish.yml:1131) green in all three environments after the CD retarget. - **Dependencies**: WP-PKG-09; WP-RN-05 (operator's renamed package identity).
- **Size**: L
WP-PKG-11: Channel/train semantics unified across all artifact classes
- **Objective**: Implement §5: one channel vocabulary (
stable/staging/dev) mapped onto the existing branch/dist-tag scheme across npm dist-tags,fabric-distarchive channels, desktop manifests, and chart OCI co-tags; exact-commit stable promotion enforced; concurrency separation between dev and staging/stable publication. - **Affected paths**:
scripts/lib/release-version.cjs:44-46(channel alias layer — branch mapping unchanged, user-facing names added); the private publish workflow (promotion job:stablerelease verifies its commit equals a publishedfabric/staging/v*tag's commit and refuses otherwise; concurrency groups pert3:.github/workflows/release.yml:29-37pattern);fabric-distchannel index objects the installers resolve (per-train listing enabling theinstall.sh:145-160rails); chart co-tag push step. - **Acceptance criteria**: (1) a
stablerelease attempt from a commit never published tostagingfails with a targeted error (negative test using a scratch commit); the passing path records the promoted staging version in the release notes metadata; (2) for one full train, the same version string is resolvable by channel across all four artifact classes (npm dist-tag, archive channel index, desktop manifest, chart co-tag) — cross-class consistency test; (3)FABRIC_CHANNEL=stableinstalls can never resolve commit-pinned prereleases (rail test shared with WP-PKG-04 AC3); (4) a dev-channel publication running concurrently with a staging release neither blocks nor corrupts it (concurrency-group test); (5)docs/VERSIONING.md(WP-PKG-02) gains the channel table and the no-nightly-cron rationale verbatim from §5. - **Test requirements**: promotion-guard negative test in CI; cross-class channel consistency check in the release lane; resolver unit tests over the alias layer (sha-suffix comparison rules untouched,
release-version.cjs:162-170). - **Dependencies**: WP-RN-07 (retargeted resolver/train is the substrate); feeds WP-PKG-03/04/09.
- **Size**: M
WP-PKG-12: Third-party license/attribution machinery (D15)
- **Objective**: Extend WP-T3-01's attribution machinery (spec 10,
10-t3code-migration.md:73,234-240) to the packaging-adopted units: installer scripts, service/launcher/self-update code, archive/npm-platform-package build scripts, and update-manifest tooling adapted from t3code (MIT, T3 Tools Inc., 2026 —00-intent.md:35; R3 spec input 10) all carry attribution; every shipped artifact class embeds the third-party notices. - **Affected paths**: the license config + sync script adopted by WP-T3-01 (extended with the packaging units:
scripts/install/*,scripts/build-archives/*,scripts/build-npm-platform-packages/*,apps/environment/src/launcher/*,apps/environment/src/cli/{service,uninstall}.ts);THIRD-PARTY-NOTICESembedding: archives ship it at root, npm platform packages include it, installers print a pointer, the chart ships it in the OCI package. - **Acceptance criteria**: (1) the license sync check fails when a file adapted from
t3:scripts/*ort3:apps/server/src/{serviceLauncher.ts,cloud/*,cli/service.ts}lacks its attribution entry (negative fixture); (2) every Form 1 archive and every npm platform package of a train contains the notices file (verifier assertion); (3) the notices name t3code's MIT license and T3 Tools Inc. copyright for each adapted unit, enumerated file-by-file; (4) chart OCI package carries the notices (unpack assertion in the chart gate). - **Test requirements**: license-sync CI check with negative fixture; packed-artifact and archive verifier assertions.
- **Dependencies**: WP-T3-01 (the machinery exists); touches WP-PKG-03/04/05/06/07/09 outputs as they land.
- **Size**: S
WP-PKG-13: Private artifact channels — provisioning + publish-isolation extension (PG1–PG5)
- **Objective**: Provision the two new AR repos (
fabric-distgeneric,fabric-chartsOCI/Docker) inboot-464019/us-central1with WIF-writer for CI and reader-by-default for humans (mirroring spec 15 §4.1's npm IAM posture); implement guards PG1–PG5 (§6) so no packaging artifact can ever ship to a public endpoint; delete the inert ghcr-push workflow. - **Affected paths**: GCP project
boot-464019(two AR repos + IAM bindings); the WP-RN-04 CI enforcement job (ci.ymlguard job extended with PG1/PG3/PG5 assertions + negative fixtures);packages/kradle/core/.github/workflows/publish.yml(deleted — PG2; its only live-relevant line was the never-executinghelm push … oci://ghcr.io/…at:94);docs/PACKAGING-DECISIONS.md(artifact-home table §1.3 recorded); repo secret audit note (PG5). - **Acceptance criteria**: (1) authenticated upload+download smoke against both new repos from CI via WIF and from a developer machine following only the written doc (mirroring spec 15 WP-RN-03 AC2); (2) PG1: a fixture workflow snippet pushing to ghcr/npmjs/public releases trips the guard (red-until-green demonstrated); (3) PG2: the nested workflow file is gone and the guard fails if any
.github/workflows/**file reappears under a package directory with publish verbs; (4) PG3: shipped installer scripts contain onlypkg.dev/FABRIC_DIST_BASE_URL-derived endpoints (static assertion in the installer test suite); (5) PG5: secret audit recorded — no ghcr/npmjs/public-release credential exists in fabric repo secrets; a CI assertion fails on any workflow consuming such a secret name; (6) IAM review recorded: CI SA writer on both repos, humans reader-by-default. - **Test requirements**: guard job negative-fixture suite (one per PG); provisioning smoke as a workflow-dispatch job; secret-scan over workflow files.
- **Dependencies**: WP-RN-03 (project/WIF/npm-repo precedent), WP-RN-04 (the guard job this extends). First mover for this spec: WP-PKG-03/07/09/14 depend on it.
- **Size**: M
WP-PKG-14: Relay artifact class & production rollout — deploy pipeline, channel promotion, runbook execution, guard coverage
- **Objective**: Own the hosted relay's path to production — the hand-off WP-T3-31 documents "(Worker + Postgres + cloudflared) … for spec 40/50 consumption" (
10-t3code-migration.md:476) and WP-FN-16 FR-7 requires carried by specs 40/50 (20-functionality-coverage.md:111): a repeatable, train-versioned deploy pipeline for the Cloudflare Worker + managed Postgres + cloudflared stack, per-channel environment promotion under §5 discipline, executed deploy/rollback runbook procedures, and publish-guard coverage so relay deployment can never leak onto a public distribution surface. - **Affected paths**:
infra/relay/deploy tooling (per-environment wrangler configuration; invocation of the@effect/sql-pg/drizzle migration runner — relay sources per WP-T3-31,10-t3code-migration.md:476); the private publish workflow (spec 15 §4.2 — new relay deploy jobs fordev/staging/prodin their own §5 concurrency group); Cloudflare account + managed-Postgres provisioning records and credential setup (Worker-scoped deploy token; migration-role DB credential; PG5-pattern secret-audit entries);docs/remote-access-runbook.mddeployment/rollback sections (runbook ownership stays with WP-FN-16 — this WP writes and executes its deploy/rollback procedures); the WP-RN-04 CI enforcement job (PG1 extension for relay deploy targets);docs/PACKAGING-DECISIONS.md(the §1.3 relay-home row recorded). - **Acceptance criteria**: (1) **channel promotion**: relay deploys are train-versioned and channel-mapped per §5 —
devandstagingrelay environments deploy from their trains, and the production relay only ever deploys a build whose exact commit a staging relay already ran (the §5 exact-commit promotion rule applied to the relay class; negative test: a prod deploy attempt from a commit never deployed to staging is refused with a targeted error); (2) **staging is the proving ground**: the staging relay deployment that WP-T3-31 AC1's broker e2e and WP-FN-16 AC7's runbook execution run against is produced by this pipeline (workflow-dispatch or train publication), not hand-rolled — asserted by deploy-provenance metadata (train tag + commit) on the staging Worker; (3) **migrations gated**: Postgres schema migrations run as an explicit, logged pipeline step; the migration up/down fixture passes in staging before any prod promotion (pipeline ordering asserted); (4) **rollback proven**: following the runbook's rollback procedure restores the previous Worker version + compatible schema — demonstrated in staging by fault injection (deploy a known-bad build, roll back, WP-T3-31's broker e2e green again); (5) **guard coverage (PG1 extension)**: the WP-RN-04 guard job asserts relay deploy jobs target only the fabric-controlled Cloudflare account (account-id allowlist) and that no workflow publishes the Worker or any relay artifact to a public marketplace/registry endpoint — a deliberately-bad fixture snippet trips it (red-until-green demonstrated, same discipline as WP-PKG-13 AC2); (6) **credential posture (PG5 pattern)**: the Cloudflare deploy token is scoped to the relay Worker and the DB credential to the migration role; both are recorded in the PG5 secret audit and a CI assertion fails if any non-relay workflow consumes them; (7) **environment-side channel consistency**: the cloudflared managed-tunnel/relay endpoints that environments consume (WP-T3-31'sapps/environment/src/cloud/activation) resolve per channel from the same §5 vocabulary — the WP-PKG-11 AC2 cross-class consistency check is extended to cover relay endpoints as a fifth class; (8) **steady-state observability**: the production relay emits a minimum production signal set — Worker error rate, bootstrap-grant failure count, cloudflared managed-tunnel liveness, and managed-Postgres disk-utilization + connection-count — each wired to an alert routed to the fabric on-call channel with a documented threshold in the runbook's observability section; a synthetic fault (forced bootstrap-grant failure and a stopped tunnel in staging) fires the corresponding alert (fault-injection test), so the production rollout ships with alerting, not just deploy/rollback tooling. - **Test requirements**: post-deploy staging smoke reusing WP-T3-31's broker e2e as the gate; migration up/down fixture; staged rollback fault-injection drill (AC4); PG1-extension negative fixture in the guard suite; the WP-FN-16 AC7 runbook execution consumes this pipeline's staging deployment (cross-spec, asserted there).
- **Dependencies**: staged per the roadmap's stage split (spec 50 §3 note 5) — **stage 1** (deploy pipeline): WP-T3-31 (relay code + documented ops story exist); **stage 2** (production rollout): WP-PKG-11 (channel vocabulary + exact-commit promotion rule), WP-PKG-13 (guard-job framework + PG discipline). Consumed by WP-FN-16 (FR-7 acceptance runs against this WP's staging/prod deployments).
- **Size**: M
---
8. Cross-spec obligations honored (D21 checklist)
- **Spec 15 §7 row for this spec, satisfied**: P-01 (→WP-PKG-01) lands with RN-1 in one major bump; P-07 (→WP-PKG-07) publishes to AR only; P-09/P-13 (→WP-PKG-09/13) inherit §3.6 chart names and the §4.2 artifact-home decision; no packaging WP creates a public-npm — or any public — channel for
@fabricstack/*(§6 extends the guard set beyond npm). - **Spec 10 hand-offs consumed**: row 12's install/service/self-update landing (WP-PKG-03…08); WP-T3-26's desktop artifacts productized (§3.7); WP-T3-28's bootstrap-download unblocked by WP-PKG-03; WP-T3-32's Node floors encoded (§1.4); WP-T3-31's relay ops story (Worker + Postgres + cloudflared, documented "for spec 40/50 consumption") productized as WP-PKG-14.
- **Spec 20 hand-offs consumed**: WP-FN-02 AC4 and WP-FN-03 AC2's default key provisioning ("spec 40 P-04") now has its concrete owner — WP-PKG-04 AC8 (first-boot generation into
$FABRIC_HOME/keys/) plus WP-PKG-05 AC7 (service-start refusal without keys, explicit opt-out preserving the WP-FN-02 AC3 / WP-FN-03 AC1 loud markers); WP-FN-16 FR-7's relay packaging/rollout is owned by WP-PKG-14, whose staging/prod deployments FR-7's runbook-execution acceptance runs against. - **Spec 30 (planned)**: WP-TEST-04's whole-platform e2e ("packaged install → create run → dispatch → agent session → journal → UI assertions", D7(b)) consumes WP-PKG-03/04 outputs and installs from Artifact Registry, never npmjs (spec 15 §7 row for spec 30).
- **Spec 50 (planned)**: sequencing input — WP-PKG-13 → {03 → 04 → 05 → 06 → 08, 07} and {09 → 10} after WP-RN-12; WP-PKG-14 after WP-T3-31 + WP-PKG-11/13 (relay rollout cannot precede the relay or the guard/channel substrate); WP-PKG-01/02 ride the RN-1 train; all packaging follows migration + coverage per D11. Scope delta SD-PKG-1 (§1.6) is written for direct citation by WP-RM-03's Gate-B register — no sibling-spec edit required to carry it.
- **Public repo untouched** (intent acceptance criterion 7): no WP above modifies
a5c-ai/babysitternames, publishing, tags, or workflows; PG4 restates it as a standing guard. - **Gate-B items raised by this spec**: (a) launcher alias bin (§3.6, candidate
fabric); (b)@fabricstack/environment-<platformKey>platform-package names (§2.4) — both proposed as spec 15 §3 matrix amendments for the name lock; WP-PKG-07 is gated on (b); (c) scope delta SD-PKG-1 (§1.6, D3 narrowing: no Windows background service in v1) — recorded for the WP-RM-03 Gate-B register.
Work-package count in this spec: **14** (WP-PKG-01 … WP-PKG-14).