@rizom/ops 0.2.0-alpha.243 → 0.2.0-alpha.244
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -1
- package/dist/brains-ops.js +289 -142
- package/dist/deploy.js +59 -59
- package/dist/images.d.ts +3 -3
- package/dist/index.d.ts +1 -0
- package/dist/index.js +315 -168
- package/dist/legacy-pilot-migration.d.ts +11 -0
- package/dist/load-registry.d.ts +7 -5
- package/dist/reconcile-dry-run.d.ts +9 -0
- package/dist/schema.d.ts +39 -33
- package/dist/secrets-encrypt.d.ts +2 -0
- package/dist/stage-legacy-crossover.d.ts +20 -0
- package/dist/verify-user.d.ts +1 -1
- package/package.json +3 -2
- package/templates/rover-pilot/.env.schema +7 -0
- package/templates/rover-pilot/README.md +6 -3
- package/templates/rover-pilot/deploy/scripts/decrypt-user-secrets.ts +2 -0
- package/templates/rover-pilot/deploy/scripts/validate-secrets.ts +11 -0
- package/templates/rover-pilot/docs/canonical-crossover-record.md +103 -0
- package/templates/rover-pilot/docs/onboarding-checklist.md +8 -12
- package/templates/rover-pilot/docs/operator-playbook.md +69 -50
- package/templates/rover-pilot/docs/user-onboarding.md +39 -68
- package/templates/rover-pilot/pilot.yaml +2 -3
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type CohortConfig, type PilotConfig } from "./schema";
|
|
2
|
+
/** Offline-only conversion. Active desired-state loading never calls this. */
|
|
3
|
+
export declare function migrateLegacyPilotConfig(input: unknown): PilotConfig;
|
|
4
|
+
/** Offline-only cohort conversion. */
|
|
5
|
+
export declare function migrateLegacyCohortConfig(input: unknown): CohortConfig;
|
|
6
|
+
/** Offline-only YAML rewrite that retains comments while changing the schema. */
|
|
7
|
+
export declare function migrateLegacyPilotYaml(input: string): string;
|
|
8
|
+
/** Offline-only cohort rewrite that retains comments while changing selection. */
|
|
9
|
+
export declare function migrateLegacyCohortYaml(input: string): string;
|
|
10
|
+
export declare function renderPilotConfig(config: PilotConfig): string;
|
|
11
|
+
export declare function renderCohortConfig(config: CohortConfig): string;
|
package/dist/load-registry.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type PilotConfig, type
|
|
1
|
+
import { type PilotConfig, type CanonicalBundleId } from "./schema";
|
|
2
2
|
export type ExternalStatus = "unknown" | "ready" | "failed";
|
|
3
3
|
export type SnapshotStatus = "present" | "missing";
|
|
4
4
|
export interface ObservedUserStatus {
|
|
@@ -11,7 +11,9 @@ export interface ResolvedCohort {
|
|
|
11
11
|
id: string;
|
|
12
12
|
members: string[];
|
|
13
13
|
brainVersionOverride?: string;
|
|
14
|
-
|
|
14
|
+
bundlesOverride?: CanonicalBundleId[];
|
|
15
|
+
addOverride?: string[];
|
|
16
|
+
removeOverride?: string[];
|
|
15
17
|
aiApiKeyOverride?: string;
|
|
16
18
|
gitSyncTokenOverride?: string;
|
|
17
19
|
}
|
|
@@ -74,13 +76,13 @@ export interface ResolvedUserIdentity {
|
|
|
74
76
|
handle: string;
|
|
75
77
|
cohort: string;
|
|
76
78
|
brainVersion: string;
|
|
77
|
-
|
|
78
|
-
|
|
79
|
+
bundles: CanonicalBundleId[];
|
|
80
|
+
add: string[];
|
|
81
|
+
remove: string[];
|
|
79
82
|
domain: string;
|
|
80
83
|
cloudflareZoneId?: string | undefined;
|
|
81
84
|
contentRepo: string;
|
|
82
85
|
profileKind?: string | undefined;
|
|
83
|
-
addOverride?: string[];
|
|
84
86
|
siteOverride?: ResolvedSiteOverride;
|
|
85
87
|
discordEnabled: boolean;
|
|
86
88
|
discordAnchorUserId?: string;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export interface ReconcileDryRunResult {
|
|
2
|
+
firstPassChangedFiles: string[];
|
|
3
|
+
secondPassChangedFiles: string[];
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Reconcile an isolated copy twice with external content-repo access disabled.
|
|
7
|
+
* The source repository is never written.
|
|
8
|
+
*/
|
|
9
|
+
export declare function dryRunReconcileAll(rootDir: string): Promise<ReconcileDryRunResult>;
|
package/dist/schema.d.ts
CHANGED
|
@@ -1,26 +1,46 @@
|
|
|
1
1
|
import { z } from "@brains/utils/zod";
|
|
2
|
-
export declare const presetSchema: z.ZodEnum<{
|
|
3
|
-
core: "core";
|
|
4
|
-
default: "default";
|
|
5
|
-
pro: "pro";
|
|
6
|
-
}>;
|
|
7
2
|
export declare const exactVersionSchema: z.ZodString;
|
|
8
3
|
export declare const handleSchema: z.ZodString;
|
|
9
4
|
export declare const secretNameSchema: z.ZodString;
|
|
10
5
|
export declare const agePublicKeySchema: z.ZodString;
|
|
11
|
-
export declare const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
contentRepoPrefix: z.ZodString;
|
|
17
|
-
domainSuffix: z.ZodString;
|
|
18
|
-
preset: typeof presetSchema;
|
|
19
|
-
aiApiKey: z.ZodString;
|
|
20
|
-
gitSyncToken: z.ZodString;
|
|
21
|
-
contentRepoAdminToken: z.ZodString;
|
|
22
|
-
agePublicKey: z.ZodString;
|
|
6
|
+
export declare const canonicalBundleIdSchema: z.ZodEnum<{
|
|
7
|
+
core: "core";
|
|
8
|
+
site: "site";
|
|
9
|
+
publishing: "publishing";
|
|
10
|
+
team: "team";
|
|
23
11
|
}>;
|
|
12
|
+
export type CanonicalBundleId = z.output<typeof canonicalBundleIdSchema>;
|
|
13
|
+
export interface PilotConfig {
|
|
14
|
+
brainVersion: string;
|
|
15
|
+
bundles: CanonicalBundleId[];
|
|
16
|
+
add?: string[] | undefined;
|
|
17
|
+
remove?: string[] | undefined;
|
|
18
|
+
githubOrg: string;
|
|
19
|
+
contentRepoPrefix: string;
|
|
20
|
+
domainSuffix: string;
|
|
21
|
+
aiApiKey: string;
|
|
22
|
+
gitSyncToken: string;
|
|
23
|
+
contentRepoAdminToken: string;
|
|
24
|
+
agePublicKey: string;
|
|
25
|
+
}
|
|
26
|
+
export interface SiteOverrideConfig {
|
|
27
|
+
package: string;
|
|
28
|
+
version: string;
|
|
29
|
+
theme?: string | undefined;
|
|
30
|
+
themeVersion?: string | undefined;
|
|
31
|
+
}
|
|
32
|
+
export interface CohortConfig {
|
|
33
|
+
members: string[];
|
|
34
|
+
brainVersionOverride?: string | undefined;
|
|
35
|
+
bundlesOverride?: CanonicalBundleId[] | undefined;
|
|
36
|
+
addOverride?: string[] | undefined;
|
|
37
|
+
removeOverride?: string[] | undefined;
|
|
38
|
+
aiApiKeyOverride?: string | undefined;
|
|
39
|
+
gitSyncTokenOverride?: string | undefined;
|
|
40
|
+
}
|
|
41
|
+
/** Sole active desired-state contract for the canonical brain. */
|
|
42
|
+
export declare const pilotSchema: z.ZodType<PilotConfig>;
|
|
43
|
+
export declare const cohortSchema: z.ZodType<CohortConfig>;
|
|
24
44
|
declare const anchorProfileSocialLinkSchema: z.ZodObject<{
|
|
25
45
|
platform: z.ZodEnum<{
|
|
26
46
|
github: "github";
|
|
@@ -70,12 +90,7 @@ declare const atprotoSchema: z.ZodObject<{
|
|
|
70
90
|
lexiconAuthority: z.ZodOptional<z.ZodBoolean>;
|
|
71
91
|
jetstream: z.ZodOptional<typeof atprotoJetstreamSchema>;
|
|
72
92
|
}>;
|
|
73
|
-
declare const siteOverrideSchema: z.
|
|
74
|
-
package: z.ZodString;
|
|
75
|
-
version: z.ZodOptional<typeof exactVersionSchema>;
|
|
76
|
-
theme: z.ZodOptional<z.ZodString>;
|
|
77
|
-
themeVersion: z.ZodOptional<typeof exactVersionSchema>;
|
|
78
|
-
}>;
|
|
93
|
+
export declare const siteOverrideSchema: z.ZodType<SiteOverrideConfig>;
|
|
79
94
|
declare const playbooksSchema: z.ZodObject<{
|
|
80
95
|
onboarding: z.ZodOptional<z.ZodBoolean>;
|
|
81
96
|
}>;
|
|
@@ -92,24 +107,15 @@ export declare const userSchema: z.ZodObject<{
|
|
|
92
107
|
contentRepoOverride: z.ZodOptional<z.ZodString>;
|
|
93
108
|
profileKind: z.ZodOptional<z.ZodString>;
|
|
94
109
|
addOverride: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
110
|
+
removeOverride: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
95
111
|
siteOverride: z.ZodOptional<typeof siteOverrideSchema>;
|
|
96
112
|
setup: z.ZodOptional<typeof setupDeliverySchema>;
|
|
97
113
|
atproto: z.ZodOptional<typeof atprotoSchema>;
|
|
98
114
|
playbooks: z.ZodOptional<typeof playbooksSchema>;
|
|
99
115
|
anchorProfile: z.ZodOptional<typeof anchorProfileSchema>;
|
|
100
116
|
}>;
|
|
101
|
-
export declare const cohortSchema: z.ZodObject<{
|
|
102
|
-
members: z.ZodArray<z.ZodString>;
|
|
103
|
-
brainVersionOverride: z.ZodOptional<z.ZodString>;
|
|
104
|
-
presetOverride: z.ZodOptional<typeof presetSchema>;
|
|
105
|
-
aiApiKeyOverride: z.ZodOptional<z.ZodString>;
|
|
106
|
-
gitSyncTokenOverride: z.ZodOptional<z.ZodString>;
|
|
107
|
-
}>;
|
|
108
|
-
export type PilotConfig = z.output<typeof pilotSchema>;
|
|
109
117
|
export type PilotConfigInput = z.input<typeof pilotSchema>;
|
|
110
118
|
export type UserConfig = z.output<typeof userSchema>;
|
|
111
119
|
export type UserConfigInput = z.input<typeof userSchema>;
|
|
112
|
-
export type CohortConfig = z.output<typeof cohortSchema>;
|
|
113
120
|
export type CohortConfigInput = z.input<typeof cohortSchema>;
|
|
114
|
-
export type PilotPreset = z.output<typeof presetSchema>;
|
|
115
121
|
export {};
|
|
@@ -2,6 +2,8 @@ import { z } from "@brains/utils/zod";
|
|
|
2
2
|
declare const encryptedUserSecretsSchema: z.ZodObject<{
|
|
3
3
|
gitSyncToken: z.ZodOptional<z.ZodString>;
|
|
4
4
|
discordBotToken: z.ZodOptional<z.ZodString>;
|
|
5
|
+
discordPublicKey: z.ZodOptional<z.ZodString>;
|
|
6
|
+
discordApplicationId: z.ZodOptional<z.ZodString>;
|
|
5
7
|
aiApiKey: z.ZodOptional<z.ZodString>;
|
|
6
8
|
atprotoAppPassword: z.ZodOptional<z.ZodString>;
|
|
7
9
|
certificatePem: z.ZodOptional<z.ZodString>;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type SiteOverrideConfig } from "./schema";
|
|
2
|
+
export interface StagedCrossover {
|
|
3
|
+
outputDir: string;
|
|
4
|
+
changedFiles: string[];
|
|
5
|
+
}
|
|
6
|
+
interface ReviewedSitePinManifest {
|
|
7
|
+
sites: Record<string, SiteOverrideConfig>;
|
|
8
|
+
}
|
|
9
|
+
export type ReviewedSitePins = ReviewedSitePinManifest["sites"];
|
|
10
|
+
export interface StageLegacyCrossoverOptions {
|
|
11
|
+
sitePins?: ReviewedSitePins | undefined;
|
|
12
|
+
}
|
|
13
|
+
export declare function parseReviewedSitePins(input: string): ReviewedSitePins;
|
|
14
|
+
export declare function loadReviewedSitePins(filePath: string): Promise<ReviewedSitePins>;
|
|
15
|
+
/**
|
|
16
|
+
* Build a complete review copy without mutating the source repository.
|
|
17
|
+
* This is offline migration tooling; active desired-state loading is canonical-only.
|
|
18
|
+
*/
|
|
19
|
+
export declare function stageLegacyCrossover(sourceDir: string, outputDir: string, options?: StageLegacyCrossoverOptions): Promise<StagedCrossover>;
|
|
20
|
+
export {};
|
package/dist/verify-user.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rizom/ops",
|
|
3
|
-
"version": "0.2.0-alpha.
|
|
3
|
+
"version": "0.2.0-alpha.244",
|
|
4
4
|
"description": "Operator CLI for managing private brain fleet registry repos",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"brains",
|
|
@@ -17,7 +17,8 @@
|
|
|
17
17
|
},
|
|
18
18
|
"bugs": "https://github.com/rizom-ai/brains/issues",
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"age-encryption": "^0.3.0"
|
|
20
|
+
"age-encryption": "^0.3.0",
|
|
21
|
+
"yaml": "^2.9.0"
|
|
21
22
|
},
|
|
22
23
|
"devDependencies": {
|
|
23
24
|
"@brains/deploy-support": "workspace:*",
|
|
@@ -25,6 +25,13 @@ CONTENT_REPO_ADMIN_TOKEN=
|
|
|
25
25
|
# @sensitive
|
|
26
26
|
DISCORD_BOT_TOKEN=
|
|
27
27
|
|
|
28
|
+
# Stored with the per-user Discord credentials and injected at deploy time.
|
|
29
|
+
# @sensitive
|
|
30
|
+
DISCORD_PUBLIC_KEY=
|
|
31
|
+
|
|
32
|
+
# @sensitive
|
|
33
|
+
DISCORD_APPLICATION_ID=
|
|
34
|
+
|
|
28
35
|
# AT Protocol publishing/discovery (optional, per-user)
|
|
29
36
|
# Comes from the decrypted users/<handle>.secrets.yaml.age file when configured.
|
|
30
37
|
# @sensitive
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
#
|
|
1
|
+
# private brain pilot
|
|
2
2
|
|
|
3
|
-
Private desired-state
|
|
3
|
+
Private desired-state repository for the hosted brain pilot. The generated repository/service name may remain `rover-pilot` for deployment identity compatibility.
|
|
4
4
|
|
|
5
5
|
This is a single operator-owned repo. Pilot users do not get their own brain repos.
|
|
6
6
|
Per-user deploy config lives under `users/<handle>/`, while content stays in per-user content repos.
|
|
@@ -31,7 +31,7 @@ The repo also checks in its deploy contract:
|
|
|
31
31
|
`.env.schema` is the single source of truth for required and sensitive deploy vars.
|
|
32
32
|
Use separate GitHub tokens: `CONTENT_REPO_ADMIN_TOKEN` for operator-side content repo creation/checks, and `GIT_SYNC_TOKEN` for runtime directory-sync git access.
|
|
33
33
|
The default pilot image tag is `brain-${brainVersion}` end to end. A user with `siteOverride` gets an isolated `brain-${brainVersion}-sites-${packageHash}` image instead.
|
|
34
|
-
When the effective brain version (`pilot.yaml.brainVersion`, or a cohort override) changes and you push, CI rebuilds the required default/site tags, refreshes generated user env files, and redeploys affected users.
|
|
34
|
+
When the effective brain version (`pilot.yaml.brainVersion`, or a cohort override) changes and you push, CI rebuilds the required default/site tags, refreshes generated user env files, and redeploys affected users. Every external site and theme package uses its own required exact version pin; ops never infers either version from the brain or from another package.
|
|
35
35
|
When a push changes only deploy contract files, CI prints `No affected user configs; skipping deploy.` and stops before Kamal.
|
|
36
36
|
|
|
37
37
|
## Commands
|
|
@@ -46,3 +46,6 @@ When a push changes only deploy contract files, CI prints `No affected user conf
|
|
|
46
46
|
- `brains-ops secrets:encrypt <repo> <handle>`
|
|
47
47
|
- `brains-ops reconcile-cohort <repo> <cohort>`
|
|
48
48
|
- `brains-ops reconcile-all <repo>`
|
|
49
|
+
- `brains-ops reconcile-all <repo> --dry-run` (isolated, no external content-repository access)
|
|
50
|
+
|
|
51
|
+
Use `docs/canonical-crossover-record.md` to record exact forward and rollback artifact pins before an approved crossover window.
|
|
@@ -25,6 +25,8 @@ writeSecretGitHubEnv(
|
|
|
25
25
|
secrets["cmsContentRepoPat"] ?? secrets["gitSyncToken"],
|
|
26
26
|
);
|
|
27
27
|
writeSecretGitHubEnv("DISCORD_BOT_TOKEN", secrets["discordBotToken"]);
|
|
28
|
+
writeSecretGitHubEnv("DISCORD_PUBLIC_KEY", secrets["discordPublicKey"]);
|
|
29
|
+
writeSecretGitHubEnv("DISCORD_APPLICATION_ID", secrets["discordApplicationId"]);
|
|
28
30
|
// Per-user AT Protocol publishing credential (optional; from the user's
|
|
29
31
|
// encrypted secrets file). The scaffold wires this so a pilot can publish its
|
|
30
32
|
// brain's agent card to its PDS; a deployment that doesn't publish simply
|
|
@@ -14,6 +14,17 @@ for (const key of requiredKeys) {
|
|
|
14
14
|
}
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
+
const discordKeys = [
|
|
18
|
+
"DISCORD_BOT_TOKEN",
|
|
19
|
+
"DISCORD_PUBLIC_KEY",
|
|
20
|
+
"DISCORD_APPLICATION_ID",
|
|
21
|
+
];
|
|
22
|
+
if (discordKeys.some((key) => process.env[key])) {
|
|
23
|
+
for (const key of discordKeys) {
|
|
24
|
+
if (!process.env[key]) missing.push(key);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
17
28
|
if (missing.length > 0) {
|
|
18
29
|
throw new Error(`Missing required secrets: ${missing.join(", ")}`);
|
|
19
30
|
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# Canonical Crossover Approval Record
|
|
2
|
+
|
|
3
|
+
> Draft evidence record only. Completing this file does not authorize a merge, publish, reconcile, or deployment.
|
|
4
|
+
|
|
5
|
+
Do not include secret values, private keys, access tokens, or decrypted user configuration.
|
|
6
|
+
|
|
7
|
+
## Authorization and freeze
|
|
8
|
+
|
|
9
|
+
- Operator:
|
|
10
|
+
- Approval reference:
|
|
11
|
+
- Approved window:
|
|
12
|
+
- Freeze start:
|
|
13
|
+
- Build workflow disabled and idle:
|
|
14
|
+
- Reconcile workflow disabled and idle:
|
|
15
|
+
- Deploy workflow disabled and idle:
|
|
16
|
+
|
|
17
|
+
## Reviewed inputs
|
|
18
|
+
|
|
19
|
+
- `brains` crossover commit:
|
|
20
|
+
- Private-pilot source commit used for staging:
|
|
21
|
+
- Canonical review commit:
|
|
22
|
+
- Secret-free review diff SHA-256:
|
|
23
|
+
- Identity-review evidence SHA-256:
|
|
24
|
+
- Reviewed hosted-site pin manifest SHA-256:
|
|
25
|
+
- Source and review worktrees clean:
|
|
26
|
+
|
|
27
|
+
## Hosted site and theme pins
|
|
28
|
+
|
|
29
|
+
List every hosted site. External site and theme package versions must be exact and must
|
|
30
|
+
match the staged user desired state; do not infer them from the brain or from each other.
|
|
31
|
+
|
|
32
|
+
| Handle | Site package | Exact site version | Theme package | Exact theme version | Package/image evidence |
|
|
33
|
+
| ------ | ------------ | ------------------ | ------------- | ------------------- | ---------------------- |
|
|
34
|
+
| | | | | | |
|
|
35
|
+
|
|
36
|
+
## Forward artifact pins
|
|
37
|
+
|
|
38
|
+
| Artifact | Exact version | Registry integrity or digest | Verified installable |
|
|
39
|
+
| ---------------------- | ------------- | ---------------------------- | -------------------- |
|
|
40
|
+
| `@rizom/brain` | | | |
|
|
41
|
+
| `@rizom/ops` | | | |
|
|
42
|
+
| private-pilot lockfile | n/a | | |
|
|
43
|
+
|
|
44
|
+
Record every image. Tags alone are not immutable evidence.
|
|
45
|
+
|
|
46
|
+
| Cohort/handle | Config commit | Image repository | Image tag | Image digest |
|
|
47
|
+
| ------------- | ------------- | ---------------- | --------- | ------------ |
|
|
48
|
+
| | | | | |
|
|
49
|
+
|
|
50
|
+
## Rollback pair
|
|
51
|
+
|
|
52
|
+
- Prior private-pilot commit:
|
|
53
|
+
- Prior `@rizom/ops` version and registry integrity:
|
|
54
|
+
|
|
55
|
+
| Cohort/handle | Prior config commit | Prior image repository | Prior image tag | Prior image digest |
|
|
56
|
+
| ------------- | ------------------- | ---------------------- | --------------- | ------------------ |
|
|
57
|
+
| | | | | |
|
|
58
|
+
|
|
59
|
+
## Identity review
|
|
60
|
+
|
|
61
|
+
- Repository and image names unchanged or explicitly approved:
|
|
62
|
+
- GitHub organization and content repository identities unchanged:
|
|
63
|
+
- Server, domain, Cloudflare zone, and ATProto identities unchanged:
|
|
64
|
+
- Secret selector names and encrypted secret artifacts unchanged:
|
|
65
|
+
- Per-user runtime-version and site-package tag inputs reviewed:
|
|
66
|
+
- No plaintext source secrets copied into the review artifact:
|
|
67
|
+
- No removed model, preset, or old-format schema discriminator remains:
|
|
68
|
+
- Every external hosted site and theme package has an exact reviewed pin:
|
|
69
|
+
|
|
70
|
+
## Offline convergence
|
|
71
|
+
|
|
72
|
+
Command:
|
|
73
|
+
|
|
74
|
+
```sh
|
|
75
|
+
bunx brains-ops reconcile-all <canonical-review-copy> --dry-run
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
- First-pass changed files:
|
|
79
|
+
- Second-pass changed files (must be zero):
|
|
80
|
+
- Review copy unchanged:
|
|
81
|
+
- External content-repository access blocked:
|
|
82
|
+
|
|
83
|
+
## Pre-window validation
|
|
84
|
+
|
|
85
|
+
- Package tests, typecheck, lint, build, and packed-consumer startup:
|
|
86
|
+
- Architecture and dependency boundaries:
|
|
87
|
+
- Environment-schema and workspace checks:
|
|
88
|
+
- Crossover migration and comment preservation:
|
|
89
|
+
- Canary-first order and health checks reviewed:
|
|
90
|
+
- Paired rollback reviewed:
|
|
91
|
+
|
|
92
|
+
## Execution record
|
|
93
|
+
|
|
94
|
+
Complete only inside the explicitly approved maintenance window.
|
|
95
|
+
|
|
96
|
+
- Unified packages published and verified:
|
|
97
|
+
- Canonical desired state committed while automation remained frozen:
|
|
98
|
+
- Image digest set matched this record:
|
|
99
|
+
- First reconcile reviewed:
|
|
100
|
+
- Per-instance health, MCP authorization, identity, content, and site checks:
|
|
101
|
+
- Second reconcile produced zero drift and no deploy work:
|
|
102
|
+
- Freeze lifted:
|
|
103
|
+
- One-week soak start:
|
|
@@ -23,18 +23,14 @@
|
|
|
23
23
|
10. Run `bunx brains-ops secrets:encrypt <repo> <handle>`.
|
|
24
24
|
11. Commit and push `users/<handle>.secrets.yaml.age`.
|
|
25
25
|
12. Run `bunx brains-ops onboard <repo> <handle>`.
|
|
26
|
-
13. Verify the deployed
|
|
27
|
-
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
- background jobs are not repeatedly failing, except for expected missing optional integrations
|
|
35
|
-
- for `presetOverride: default` users:
|
|
36
|
-
- initial site build completes
|
|
37
|
-
14. For fleet upgrades, edit `pilot.yaml.brainVersion` and push once; CI rebuilds the required default/site image tags, refreshes generated user env files, and redeploys affected users. Site overrides follow the user's effective brain version unless they declare an explicit exact `version` pin.
|
|
26
|
+
13. Verify the deployed canonical contract:
|
|
27
|
+
- `https://<handle>.rizom.ai/health` returns `200`
|
|
28
|
+
- `https://<handle>.rizom.ai/chat` loads the web chat and accepts passkey sign-in
|
|
29
|
+
- unauthenticated `POST https://<handle>.rizom.ai/mcp` returns the expected auth failure
|
|
30
|
+
- content repo exists and runtime sync is healthy
|
|
31
|
+
- background jobs are not repeatedly failing, except for expected missing optional integrations
|
|
32
|
+
- when `site` is selected, the browser/CMS surfaces load and the initial app-managed site build completes
|
|
33
|
+
14. For fleet upgrades, edit `pilot.yaml.brainVersion` and push once; CI rebuilds the required default/site image tags, refreshes generated user env files, and redeploys affected users. Every external site and theme package keeps its own required exact version pin and never follows the brain version implicitly.
|
|
38
34
|
15. Confirm the user received the setup email, registered their passkey, and can sign in to web chat at `https://<handle>.rizom.ai/chat`. That completes the default onboarding; everything below is per-cohort extras.
|
|
39
35
|
16. Hand over the browser surfaces:
|
|
40
36
|
- Chat (primary): `https://<handle>.rizom.ai/chat`
|
|
@@ -30,14 +30,42 @@ When `pilot.yaml.brainVersion` changes and you push:
|
|
|
30
30
|
3. deploy runs for handles whose generated config changed
|
|
31
31
|
4. generated file commits happen once in a final aggregation step after the deploy matrix finishes
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
33
|
+
Every external site and theme package has its own exact version pin. A cohort or
|
|
34
|
+
pilot brain-version bump never changes those package versions implicitly; update each
|
|
35
|
+
pin deliberately from reviewed package and image evidence.
|
|
36
36
|
|
|
37
37
|
When a push changes only deploy contract files and no generated `users/<handle>/.env` or `users/<handle>/brain.yaml` files, the deploy workflow exits through its explicit no-op path and prints `No affected user configs; skipping deploy.`
|
|
38
38
|
|
|
39
39
|
They are scaffolded from `@rizom/ops`, then versioned in this repo like any other deploy contract.
|
|
40
40
|
|
|
41
|
+
## Canonical contract crossover maintenance window
|
|
42
|
+
|
|
43
|
+
Do not run this procedure without explicit operator approval. The canonical desired state, canonical `@rizom/ops`, and unified runtime image form one contract and must move or roll back together. Complete `docs/canonical-crossover-record.md` as the approval evidence without adding secret values.
|
|
44
|
+
|
|
45
|
+
Before the window, record and review:
|
|
46
|
+
|
|
47
|
+
- the prior pilot commit and exact `@rizom/ops` version;
|
|
48
|
+
- every prior runtime image tag and immutable digest;
|
|
49
|
+
- the reviewed canonical pilot commit;
|
|
50
|
+
- the exact unified `@rizom/brain` and `@rizom/ops` versions;
|
|
51
|
+
- every unified image tag and immutable digest;
|
|
52
|
+
- canary-first rollout order, followed by the remaining cohorts;
|
|
53
|
+
- expected `/health` version, unauthenticated MCP response, site marker, and content repository identity for each posture.
|
|
54
|
+
|
|
55
|
+
Run `bunx brains-ops reconcile-all <canonical-review-copy> --dry-run` against the isolated review copy. The command blocks external content-repository access, leaves the review copy untouched, and must report second-pass zero drift.
|
|
56
|
+
|
|
57
|
+
During the approved window:
|
|
58
|
+
|
|
59
|
+
1. Freeze unrelated merges and releases. Wait for active Build, Reconcile, and Deploy runs to finish, then disable all three pilot workflows with `gh workflow disable build.yml`, `gh workflow disable reconcile.yml`, and `gh workflow disable deploy.yml`.
|
|
60
|
+
2. Publish and verify the reviewed unified runtime and matching ops artifacts. Do not update pilot desired state until the exact versions are installable and the expected images can be built.
|
|
61
|
+
3. Apply the reviewed canonical pilot revision while automation remains disabled. Confirm repository names, server/domain identity, content repositories, secret selectors, image names, and tag identity against the review diff.
|
|
62
|
+
4. Enable only Build, run it for the canonical desired-state revision, and record every resulting image digest. Stop if the observed digest set differs from the cutover record.
|
|
63
|
+
5. Enable only Reconcile, run it once, and review its generated config commit. No generated file may combine canonical config with a retired image version.
|
|
64
|
+
6. Enable Deploy and deploy one handle at a time in the approved order. After each deploy, run `bunx brains-ops verify-user . <handle>` and complete the manual identity, content-sync, and app-managed site checks.
|
|
65
|
+
7. Run Reconcile a second time. Require no generated diff and no deploy work before re-enabling normal automation and lifting the merge/release freeze.
|
|
66
|
+
|
|
67
|
+
If any gate fails, disable all three workflows again. Restore the prior pilot desired-state and dependency revision, reconcile with the prior ops version, and redeploy the prior image tag/digest as one rollback pair. Verify the prior `/health` version and identity/content/site checks before re-enabling automation. Never restore only config or only an image.
|
|
68
|
+
|
|
41
69
|
## Stale deploy lock recovery
|
|
42
70
|
|
|
43
71
|
Kamal intentionally leaves its remote deploy lock in place when a deployment is cancelled or interrupted. Confirm that no deployment for the user is still active before releasing the lock, then use the deploy workflow's explicit recovery input:
|
|
@@ -77,7 +105,7 @@ When `@rizom/ops` changes the scaffolded deploy contract:
|
|
|
77
105
|
3. review the resulting changes to `.env.schema`, `deploy/scripts/`, and workflows in git
|
|
78
106
|
4. commit the updated deploy artifacts together
|
|
79
107
|
|
|
80
|
-
##
|
|
108
|
+
## Canonical verification notes
|
|
81
109
|
|
|
82
110
|
Use the verification script after deploy:
|
|
83
111
|
|
|
@@ -85,35 +113,31 @@ Use the verification script after deploy:
|
|
|
85
113
|
bunx brains-ops verify-user . <handle>
|
|
86
114
|
```
|
|
87
115
|
|
|
88
|
-
|
|
116
|
+
For every bundle posture it checks:
|
|
89
117
|
|
|
90
|
-
- `https://<handle>.rizom.ai/health`
|
|
91
|
-
- unauthenticated `POST https://<handle>.rizom.ai/mcp`
|
|
92
|
-
- background jobs
|
|
118
|
+
- `https://<handle>.rizom.ai/health` returns `200`;
|
|
119
|
+
- unauthenticated `POST https://<handle>.rizom.ai/mcp` returns the expected auth failure;
|
|
120
|
+
- background jobs are not repeatedly failing, except for missing optional integrations.
|
|
93
121
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
- Rover core is MCP-only; a bare `GET /` may return `401`, which does not indicate a bad deploy.
|
|
97
|
-
|
|
98
|
-
For `preset: default`, the script also checks:
|
|
99
|
-
|
|
100
|
-
- `https://<handle>.rizom.ai/` loads the browser/site surface
|
|
101
|
-
- `https://<handle>.rizom.ai/cms` loads the CMS/login surface
|
|
122
|
+
A `core`-only instance is MCP-only; a bare `GET /` may return `401` without indicating a bad deploy. When `site` is selected, verification also checks the browser and CMS/login surfaces.
|
|
102
123
|
|
|
103
124
|
Manual checks that remain:
|
|
104
125
|
|
|
105
|
-
- initial site
|
|
106
|
-
- content
|
|
107
|
-
- passkey setup/handoff is completed from the setup email
|
|
126
|
+
- initial app-managed site output is correct for the expected content/theme;
|
|
127
|
+
- content repository identity and runtime sync are healthy;
|
|
128
|
+
- passkey setup/handoff is completed from the setup email.
|
|
108
129
|
|
|
109
|
-
## One-user
|
|
130
|
+
## One-user canonical site canary
|
|
110
131
|
|
|
111
132
|
Run this before adding custom site/theme packages or rolling a larger browser/CMS-first cohort.
|
|
112
133
|
|
|
113
|
-
1. Create or choose a canary cohort with
|
|
134
|
+
1. Create or choose a canary cohort with explicit bundles:
|
|
114
135
|
|
|
115
136
|
```yaml
|
|
116
|
-
|
|
137
|
+
bundles:
|
|
138
|
+
- core
|
|
139
|
+
- site
|
|
140
|
+
- publishing
|
|
117
141
|
```
|
|
118
142
|
|
|
119
143
|
2. Add exactly one canary user to that cohort.
|
|
@@ -131,11 +155,7 @@ Run this before adding custom site/theme packages or rolling a larger browser/CM
|
|
|
131
155
|
7. Ask the user to complete passkey setup from the setup email.
|
|
132
156
|
8. Continue to visual customization only after the canary is healthy.
|
|
133
157
|
|
|
134
|
-
Rollback
|
|
135
|
-
|
|
136
|
-
- move the canary back to a core cohort, or remove `presetOverride: default` from the cohort
|
|
137
|
-
- reconcile generated outputs
|
|
138
|
-
- rebuild/redeploy the affected user
|
|
158
|
+
Rollback must restore the prior desired-state revision and prior runtime image together. Never pair canonical config with the retired image, or retired config with the canonical image.
|
|
139
159
|
|
|
140
160
|
## Hosted site and theme package contract
|
|
141
161
|
|
|
@@ -144,7 +164,7 @@ Start with the public [site mockup migration guide](https://github.com/rizom-ai/
|
|
|
144
164
|
- A site package must default-export a valid `SitePackage` and use documented public APIs such as `@rizom/brain/site`.
|
|
145
165
|
- A theme package must default-export its CSS as a string. Hosted custom themes currently use the `@rizom/*` scope so the fleet image installs them with the site package; `@brains/*` themes are bundled with `@rizom/brain`.
|
|
146
166
|
- Site and custom theme packages must be public npm packages that install without registry credentials.
|
|
147
|
-
- Site and
|
|
167
|
+
- Site, theme, and brain packages publish independently. Hosted configuration requires exact site and external-theme version pins and never derives one package version from another.
|
|
148
168
|
- Keep site structure and theme CSS in separate packages. Do not put private content or secrets in either package.
|
|
149
169
|
|
|
150
170
|
Configure a user in `users/<handle>.yaml`:
|
|
@@ -152,22 +172,20 @@ Configure a user in `users/<handle>.yaml`:
|
|
|
152
172
|
```yaml
|
|
153
173
|
siteOverride:
|
|
154
174
|
package: "@rizom/site-example"
|
|
175
|
+
version: <exact-site-version>
|
|
155
176
|
theme: "@rizom/theme-example"
|
|
156
|
-
themeVersion: <exact-theme-version>
|
|
157
|
-
# version: <exact-site-version> # optional deliberate pin
|
|
177
|
+
themeVersion: <exact-theme-version>
|
|
158
178
|
```
|
|
159
179
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
produces an isolated per-instance image; it never changes the fleet's shared
|
|
165
|
-
default image.
|
|
180
|
+
Missing external package versions fail desired-state validation. A site override
|
|
181
|
+
produces an isolated per-instance image; it never changes the fleet's shared default
|
|
182
|
+
image. Bundled `@brains/*` themes omit `themeVersion` because they are not installed as
|
|
183
|
+
separate packages.
|
|
166
184
|
|
|
167
185
|
### Custom-package canary and rollback
|
|
168
186
|
|
|
169
187
|
1. Confirm the exact site/theme versions are public-installable without npm credentials.
|
|
170
|
-
2. Apply the package names to one healthy
|
|
188
|
+
2. Apply the exact package names and versions to one healthy canonical site canary.
|
|
171
189
|
3. Reconcile the canary, push the generated output, and let build/deploy create its site image.
|
|
172
190
|
4. Run `bunx brains-ops verify-user . <handle>`.
|
|
173
191
|
5. Manually verify the site, theme, CMS, content sync, and passkey sign-in before adding more users.
|
|
@@ -246,31 +264,32 @@ Notes:
|
|
|
246
264
|
- The ATProto app password is secret and belongs only in the encrypted per-user secret payload.
|
|
247
265
|
- For smoke deployments, pin only the smoke cohort/user to the released brain version that contains ATProto support.
|
|
248
266
|
|
|
249
|
-
## Discord
|
|
267
|
+
## Discord application credential checklist
|
|
250
268
|
|
|
251
269
|
Use this when enabling Discord for a pilot user.
|
|
252
270
|
|
|
253
271
|
1. Pick the user handle (for example `smoke`).
|
|
254
272
|
2. Open the Discord Developer Portal.
|
|
255
|
-
3. Create a **new application** for that user's
|
|
273
|
+
3. Create a **new application** for that user's brain.
|
|
256
274
|
4. Add a **Bot** to the application.
|
|
257
|
-
5. Copy the bot token.
|
|
258
|
-
6. Put
|
|
275
|
+
5. Copy the bot token, application public key, and application ID.
|
|
276
|
+
6. Put those values in `.env` or `.env.local` while onboarding that user:
|
|
277
|
+
- `DISCORD_BOT_TOKEN=...`
|
|
278
|
+
- `DISCORD_PUBLIC_KEY=...`
|
|
279
|
+
- `DISCORD_APPLICATION_ID=...`
|
|
259
280
|
7. Keep `discord.enabled: true` in `users/<handle>.yaml` unless you explicitly want to disable the primary pilot interface.
|
|
260
|
-
8. Encrypt the current per-user
|
|
281
|
+
8. Encrypt the current per-user credential payload:
|
|
261
282
|
- `bunx brains-ops secrets:encrypt . <handle>`
|
|
262
283
|
9. Reconcile/deploy the user or cohort:
|
|
263
|
-
|
|
264
|
-
- `bunx brains-ops
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
11. In the Discord Developer Portal, generate an install URL and invite the bot to the right server.
|
|
268
|
-
12. Send a test message in Discord and confirm the rover responds.
|
|
284
|
+
- `bunx brains-ops onboard . <handle>`
|
|
285
|
+
- or `bunx brains-ops reconcile-cohort . <cohort>`
|
|
286
|
+
10. In the Discord Developer Portal, generate an install URL and invite the bot to the right server.
|
|
287
|
+
11. Send a test message in Discord and confirm the brain responds.
|
|
269
288
|
|
|
270
289
|
Notes:
|
|
271
290
|
|
|
272
|
-
- Use **one
|
|
273
|
-
- Do not reuse the same Discord
|
|
291
|
+
- Use **one Discord application credential set per user/brain**.
|
|
292
|
+
- Do not reuse the same Discord application across multiple pilot users.
|
|
274
293
|
- Discord is the default pilot interface moving forward.
|
|
275
294
|
- The encrypted `users/<handle>.secrets.yaml.age` file is the durable checked-in deploy input; your local env is only the operator staging source.
|
|
276
295
|
- Direct MCP client access should use OAuth/passkey-capable clients where possible.
|