Document integration hardening and staging gates

67614b5b7dc2 · Christopher David · · parent d5679e83572c

Document integration hardening and staging gates

Define blocking gates for architecture cleanup, tenant isolation, deployment safety, staging topology, regression testing, failure injection, and soak evidence. Keep production explicitly out of scope until all gates pass.

Deploy story

What this commit did to the running system — joined from the forge receipt chain, the part a commit page elsewhere cannot show.

Not deployed through the forge lane

No push, promotion, build, or deploy receipt references this commit (receipts are scanned over a bounded recent window). Changes shipped by full node replacement carry their proof in the release gate receipt instead.

Changed files

  • added docs/2026-08-20-integration-hardening-and-staging-readiness-recommendations.md

Diff

1 file changed, +938 -0

docs/2026-08-20-integration-hardening-and-staging-readiness-recommendations.md added +938

@@ -0,0 +1,938 @@

1
# Integration hardening and staging readiness recommendations
2
3
Date: 2026-08-20
4
5
Status: Proposed
6
7
## Outcome
8
9
Make `openagents.com` one coherent public AGPL application, deploy it safely to
10
staging, and prove its behavior there before any production work begins.
11
12
The complete Sarah product is intentionally part of this repository. The
13
remaining work is not to restore a private-service split. The work is to make
14
the merged application internally consistent, secure its authority and data
15
boundaries, finish the deployment system, remove obsolete source-project
16
assumptions, and establish reproducible staging evidence.
17
18
Production is out of scope. Do not deploy production traffic, mount production
19
credentials, repoint production DNS, or promote a production fleet target as
20
part of this plan.
21
22
## Executive recommendations
23
24
Complete the work in this order:
25
26
1. Establish one accurate architecture and terminology baseline.
27
2. Repair documentation, invariants, configuration, and generic runtime names.
28
3. Harden authentication, repository scoping, data ownership, and administrative
29
   authority.
30
4. Consolidate the UI, icon, Markdown, and dependency stacks.
31
5. Harden the forge build and deployment lanes before enabling hot loading.
32
6. Create an isolated staging environment that represents the intended runtime.
33
7. Run the complete staging regression and failure-injection matrix.
34
8. Hold the staging release through a soak period and close every unexplained
35
   error before discussing production.
36
37
Treat each numbered gate in this document as blocking. A later gate cannot make
38
an earlier failure acceptable.
39
40
## Ground rules
41
42
- Keep Sarah-specific identity, persona, voice, and evaluation material public
43
  under the AGPL license.
44
- Use `OpenAgents` for application infrastructure and Sarah only for the agent,
45
  persona, or behavior that is specifically Sarah's.
46
- Keep direct provider credentials on the server. The browser must never receive
47
  an OpenAI key, forge operator token, machine token, or recording key.
48
- Keep hosted CI disabled. Run checks on owned machines, local hooks, and owned
49
  deployment infrastructure.
50
- Keep all new deployment capabilities disabled by default. Enable them in
51
  staging only after their preceding gates pass.
52
- Treat PostgreSQL rows and immutable receipts as authority. Treat LiveView,
53
  PubSub, caches, and status pages as projections.
54
- Preserve historical migrations. Correct live schema problems with new
55
  migrations instead of rewriting migrations that may already have run.
56
- Use the completed Issues and Projects coverage work as an input to this plan.
57
  Do not duplicate that work or weaken its assertions to make a gate pass.
58
- Record every staging result against an exact Git SHA, image digest, artifact
59
  digest, migration version, and staging revision.
60
61
## Gate 0: Freeze and measure the baseline
62
63
Create a reliable starting point before changing architecture or infrastructure.
64
65
1. Record the current Git SHA and confirm the worktree is clean.
66
2. Run `mix precommit` on an owned test machine.
67
3. Run `mix test --only cluster` as a separate stage.
68
4. Run the JavaScript tests for voice state, recording, and browser hooks.
69
5. Run `mix test --cover` and merge its result with the separate cluster stage.
70
6. Record compile warnings, test exclusions, flaky tests, and test duration.
71
7. Build a release and run its startup path against a disposable database.
72
8. Save the result as a local, content-free gate receipt tied to the SHA.
73
74
Do not use the current green suite as evidence for untested code. The updated
75
coverage audit records strong Issues and Projects coverage and the defects it
76
found. Recovery workers and release-only entry points still need direct
77
evidence.
78
79
**Exit criteria:** The team has one reproducible baseline with no hidden test
80
filters and a named owner for every known failure or exclusion.
81
82
## Gate 1: Define the integrated architecture
83
84
Write one architecture document that describes the application that now exists.
85
It should replace the conflicting public-shell, private-service, mock-chat, and
86
partial-port narratives.
87
88
The architecture document should state these decisions:
89
90
- `openagents.com` owns the browser UI, Sarah persona, conversation and turn
91
  lifecycle, provider orchestration, tools, memory, delegated work, voice,
92
  machines, issues, projects, forge, and administrative surfaces.
93
- Provider adapters are replaceable server-side boundaries. Direct OpenAI use
94
  is an adapter choice, not an application-wide dependency.
95
- PostgreSQL is the durable product authority.
96
- The forge's Git and deployment planes are separate concerns inside one
97
  application namespace.
98
- Public, authenticated, operator, machine, and internal-service routes have
99
  distinct authorization rules.
100
- Direct BEAM load, relup, and rolling replacement are different deployment
101
  strategies with different safety requirements.
102
103
Add focused architecture decision records for:
104
105
- The full public AGPL integration.
106
- Sarah as a persona within the `OpenAgents` application.
107
- Direct provider integration and credential boundaries.
108
- GitHub identity and access-token retention.
109
- Basecoat and the application UI component system.
110
- The staging and eventual production fleet topology.
111
- Forge-canonical source control and GitHub mirroring, if that remains the
112
  intended cutover.
113
114
**Exit criteria:** A contributor can explain the application and its trust
115
boundaries without reading a superseded plan or another repository.
116
117
## Gate 2: Remove stale names and references
118
119
Apply a semantic naming rule instead of a blind search-and-replace.
120
121
### Keep Sarah references where they are accurate
122
123
Keep Sarah in names that identify Sarah-specific behavior or data, such as:
124
125
- Persona artifact IDs and source manifests.
126
- Persona evaluation corpora.
127
- User-visible Sarah identity and voice copy.
128
- Sarah-specific role or behavior revisions when the identity is part of the
129
  artifact contract.
130
131
### Rename generic application infrastructure
132
133
Rename generic infrastructure that still carries a source-project name. Review
134
at least these areas:
135
136
- `OpenAgents.Sarah.Supervisor`.
137
- `sarah_live_view`, `sarah_html`, and `sarah_html_helpers`.
138
- `assets/css/sarah.css` and comments that call the whole application Sarah.
139
- Generic test cases and helpers named `SarahConnCase`, `SarahDataCase`, or
140
  `SarahChannelCase`.
141
- `sarah_source_dir` and other generic application configuration keys.
142
- `/tmp/sarah_*` and `/var/lib/sarah/*` runtime paths.
143
- Forge defaults that name the `sarah` repository instead of
144
  `OpenAgentsInc/openagents.com`.
145
- Builder sidecar names, queue paths, WAL paths, and artifact paths.
146
- Generic user-agent strings and internal service labels.
147
148
Prefer names such as `OpenAgents.RuntimeSupervisor`, `openagents_live_view`,
149
`openagents.css`, `source_repo_dir`, and `/var/lib/openagents`. Do not rename a
150
Sarah persona artifact to OpenAgents if that would erase its actual identity.
151
152
### Establish an allowed-reference check
153
154
Add a repository check that searches source, tests, configuration, and docs for
155
`Sarah`, `sarah`, old filesystem roots, old application atoms, and retired
156
service domains. Maintain a small allowlist of intentional Sarah-specific
157
locations. Fail the check on every unclassified match.
158
159
**Exit criteria:** Every remaining Sarah reference is intentional, documented,
160
and specific to Sarah rather than inherited infrastructure.
161
162
## Gate 3: Reconcile all documentation and invariants
163
164
Documentation currently describes several incompatible generations of the
165
application. Make documentation a release gate rather than a historical
166
accident.
167
168
### Repair the top-level narrative
169
170
- Rewrite `README.md` to describe the integrated application accurately.
171
- Remove the clean-room statement because the repository intentionally contains
172
  the integrated product implementation.
173
- Replace DaisyUI references with the current Basecoat and OpenAgents style
174
  system.
175
- Separate working features, staging-only features, disabled features, and
176
  planned features. Do not describe a disabled or incomplete deploy lane as
177
  production-ready.
178
- State the AGPL licensing decision and identify vendored third-party licenses.
179
180
### Retire or rewrite obsolete plans
181
182
- Mark `docs/chat-inference-plan.md` as superseded or remove it after preserving
183
  any still-valid provider-boundary requirements.
184
- Convert `docs/sarah-integration-plan.md` into a historical migration record or
185
  replace its stale status and skipped-test counts.
186
- Close or archive `docs/2026-08-19-gap-implementation-plan.md` after moving each
187
  unresolved item into the current hardening plan.
188
- Update `docs/component-library.md` and
189
  `docs/issues-projects-ui-roadmap.md` for Basecoat, `SarahUI`, and the actual
190
  component catalog.
191
- Update `docs/github-auth-plan.md` after deciding whether GitHub access tokens
192
  remain stored.
193
- Keep the test coverage audit as a dated measurement. Add a later audit instead
194
  of rewriting the original numbers.
195
196
### Repair the invariant ledger
197
198
Review every invariant in `INVARIANTS.md` against code, schema, configuration,
199
tests, and documentation.
200
201
- Give every invariant a unique ID. Resolve the duplicate `DEGRADE-001` entries.
202
- Correct nonexistent paths such as `priv/openagents`, `OpenAgentsWeb.UI`, and
203
  `style-openagents.css`, or rename the implementation first.
204
- Resolve the contradiction between discarding GitHub tokens and the current
205
  encrypted-token storage path.
206
- Distinguish implemented invariants from proposed invariants. A proposed
207
  contract cannot claim current evidence.
208
- Port, recreate, or remove references to missing evidence documents.
209
- Ensure each current invariant names at least one executable test or a concrete
210
  manual release proof.
211
- Remove references to modules, controllers, routes, and admin recording
212
  behavior that no longer exist.
213
214
### Add documentation validation
215
216
Add an owned local check that:
217
218
- Verifies relative Markdown links and referenced local files.
219
- Detects duplicate invariant IDs.
220
- Detects banned obsolete terms such as DaisyUI and retired service domains.
221
- Detects absolute developer-specific paths such as `~/work` and
222
  `/Users/<name>`.
223
- Confirms that every evidence file named by `INVARIANTS.md` exists.
224
225
**Exit criteria:** Every current document agrees on product ownership,
226
components, authentication, deployment maturity, and staging status, and all
227
local references resolve.
228
229
## Gate 4: Harden dependencies, assets, and the component system
230
231
### Complete the Markdown parser migration
232
233
The integration plan says MDEx replaced Earmark, while the application still
234
depends on and calls Earmark. Hex marks Earmark as retired and no longer
235
maintained. Migrate to MDEx or another maintained parser before staging, then
236
verify sanitization, security history, and output compatibility.
237
238
Whichever parser remains must pass tests for:
239
240
- Raw HTML refusal.
241
- Script and event-handler removal.
242
- Unsafe URL scheme rejection.
243
- Bounded nesting, input size, and output size.
244
- Code blocks, lists, links, tables, and malformed Markdown.
245
- Stable rendering for streamed and persisted assistant text.
246
247
Remove the unused parser and update every related document in the same commit.
248
249
### Complete UI consolidation
250
251
- Choose the final generic module name: preferably `OpenAgentsWeb.UI` once the
252
  source-project migration is complete.
253
- Move all product surfaces onto that component system.
254
- Keep `CoreComponents` only as a temporary compatibility layer with an explicit
255
  removal list.
256
- Remove DaisyUI aliases and compatibility tokens after every surface has
257
  migrated.
258
- Use one application icon path. Remove Heroicons and its dependency after the
259
  remaining issue and layout surfaces use vendored icons.
260
- Keep Basecoat imports per component and do not load Basecoat JavaScript.
261
- Rename the style pack after generic application components no longer depend on
262
  the Sarah name.
263
- Make the component catalog the executable inventory of supported primitives.
264
265
### Audit dependencies and licenses
266
267
- Remove unused dependencies and stale lock entries.
268
- Run Hex retirement and vulnerability checks on owned infrastructure.
269
- Generate a dependency and license inventory for the release artifact.
270
- Verify the Basecoat, icon, font, and brand-mark attribution files.
271
- Pin every Git dependency to an immutable tag or revision.
272
- Build an SBOM for the staging image and retain it with the staging evidence.
273
274
**Exit criteria:** The application has one Markdown parser, one component
275
system, one icon path, no unexplained dependency, and complete license records.
276
277
## Gate 5: Make runtime configuration explicit and fail closed
278
279
Create one typed runtime configuration boundary. Do not scatter environment
280
parsing across feature modules.
281
282
Validate these groups at boot:
283
284
- Endpoint host, allowed origins, HTTPS aliases, and secure cookie settings.
285
- Database connection and migration behavior.
286
- GitHub OAuth client, callback, scope, and token-encryption keys.
287
- OpenAI text, voice, embedding, and shadow-program provider settings.
288
- Voice admission, recording, retention, and encryption settings.
289
- Work, machine, recovery, memory, and graph feature dependencies.
290
- Forge repository, owner, Git URL, WAL, artifact store, build sidecar, operator
291
  token, allowlist, and fleet size.
292
- Horde, Ra, DNS discovery, node naming, and distribution settings.
293
294
Apply these rules:
295
296
- Use explicit staging values instead of relying on development defaults.
297
- Refuse invalid or incomplete feature combinations. For example, refuse an
298
  enabled recording feature without a recording key.
299
- Redact values from errors and logs. Name the missing setting, not its value.
300
- Give every feature a documented default and staging override.
301
- Disable voice, work, semantic memory, forge deployment, and boot convergence
302
  until their specific staging gates begin.
303
- Do not enable Ra by default on a topology that cannot form the expected
304
  cluster.
305
- Put Ra data, forge artifacts, WAL caches, and build queues on intentional
306
  durable or disposable volumes. Do not use inherited `/tmp/sarah_*` defaults.
307
- Make the target repository `OpenAgentsInc/openagents.com` explicit.
308
309
Add a command that prints a content-free configuration readiness report. It
310
should show enabled features and validation status without printing secrets,
311
URLs with credentials, internal node names, or tokens.
312
313
**Exit criteria:** A staging release either starts with a valid, reviewed
314
configuration or exits before serving traffic with a redacted diagnostic.
315
316
## Gate 6: Harden identity, authorization, and secrets
317
318
### Decide GitHub token retention deliberately
319
320
The current OAuth path stores an encrypted GitHub token even though some
321
invariants say the token is discarded after identity projection.
322
323
Choose one model:
324
325
- **Identity-only:** request the minimum identity scope, discard the access
326
  token, and remove repository tools that depend on it.
327
- **Identity and GitHub tools:** store the token because the user explicitly
328
  enabled GitHub-backed tools, request the minimum required scopes, disclose the
329
  retention, encrypt it at rest, support key rotation, revoke it on disconnect,
330
  and include its metadata in data-rights behavior without exporting the token.
331
332
Do not describe the first model while implementing the second.
333
334
### Separate route authority classes
335
336
Classify every route as public read, authenticated browser, authenticated API,
337
operator, machine, internal service, or Git transport. Add an automated route
338
inventory test.
339
340
- Keep public health, status, leaderboard, changelog, and configured source
341
  projections content-free or bounded by their publication contract.
342
- Require authentication and authorization for every product mutation.
343
- Keep browser mutations CSRF-protected.
344
- Give CLI-compatible API clients a deliberate bearer-token or personal-access-
345
  token flow rather than relying on browser cookies.
346
- Require operator identity for promotion and other operator writes.
347
- Require scoped, expiring, replay-resistant credentials for machines and
348
  inference grants.
349
- Return indistinguishable responses for hidden operator and private repository
350
  surfaces where the invariant requires it.
351
352
### Protect secrets and logs
353
354
- Inventory every secret and assign a staging-only secret name and runtime
355
  identity.
356
- Remove secrets from build arguments, images, repository URLs, receipts, and
357
  exception text.
358
- Do not embed the forge operator token in a URL that can reach process lists or
359
  logs.
360
- Redact OAuth callback query parameters at the platform logging boundary.
361
- Scan application logs for message, prompt, transcript, memory, tool argument,
362
  SDP, credential, and machine-token leakage.
363
- Rotate any credential that may have appeared in prior staging logs.
364
365
**Exit criteria:** Every mutation has an explicit principal and scope, GitHub
366
token behavior matches its documentation, and staging logs contain no secret or
367
private-content fields.
368
369
## Gate 7: Add real repository and tenant scoping
370
371
The Issues and Projects routes carry `owner` and `repo`, but several contexts
372
currently read and mutate global tables. Fix the domain model before treating
373
the tracker as a multi-repository forge.
374
375
1. Add a canonical repository entity with owner, name, visibility, default
376
   branch, and stable ID.
377
2. Add `repository_id` foreign keys to issues, labels, milestones, comments,
378
   assignee relationships, and repository projects where appropriate.
379
3. Make issue and milestone numbers unique per repository, not globally.
380
4. Scope every lookup by repository and resource identifier in one query.
381
5. Scope labels and assignable users to repository authorization rules.
382
6. Scope project ownership and project-item issue references.
383
7. Reject cross-repository issue, label, milestone, and project identifiers.
384
8. Add database constraints that enforce the same relationships as the
385
   application.
386
9. Backfill current rows into an explicit initial repository with a reversible,
387
   rehearsed migration.
388
10. Make public reads and authenticated writes explicit rather than leaving all
389
    `/api/v3` routes on the same unauthenticated pipeline.
390
391
Build on the completed controller, LiveView, and domain coverage. Add
392
multi-repository isolation cases without duplicating the existing mount,
393
interaction, and JSON-contract cases.
394
395
**Exit criteria:** An owner or repository path can never read or mutate a row
396
owned by another repository, and PostgreSQL enforces the boundary.
397
398
## Gate 8: Harden chat, memory, work, machines, and voice
399
400
### Chat and provider lifecycle
401
402
- Keep LiveView free of direct HTTP, credential, retry, and provider event code.
403
- Bound every message, stream frame, tool call, continuation, and rendered
404
  projection.
405
- Require durable turn, message, provider-step, and tool-step state before
406
  broadcasting completion.
407
- Verify cancellation, reconnect, duplicate event, timeout, malformed provider
408
  data, and process-restart paths.
409
- Make retry behavior operation-specific. Do not retry a mutation unless its
410
  idempotency contract makes repetition safe.
411
- Confirm that every provider failure produces a bounded, provider-neutral
412
  result.
413
414
### Memory and data rights
415
416
- Test account isolation at the query and database levels for every memory
417
  plane.
418
- Confirm that recall snapshots exclude later writes and foreign accounts.
419
- Confirm that reset, export, delete, correction, forget, retention, and purge
420
  paths include all newly integrated tables.
421
- Verify that derived semantic and graph data can be rebuilt from durable
422
  authority and disappears when its authoritative source is deleted.
423
- Keep every export bounded and require a disposable staging account for
424
  destructive export and deletion tests.
425
426
### Recovery workers
427
428
Add direct tests for:
429
430
- `OpenAgents.TurnRecovery` after a turn process dies mid-stream.
431
- `OpenAgents.VoiceRecovery` after a voice runtime disappears.
432
- `OpenAgents.WorkRecovery` after a delegated worker dies at each durable
433
  checkpoint.
434
- `OpenAgents.Memory.SemanticWorker` after provider, database, and process
435
  failures.
436
437
Use process monitors, supervised processes, and durable state assertions. Do not
438
use fixed sleeps as the correctness mechanism.
439
440
### Voice and recording
441
442
- Fix the recording start race so the admitted server generation exists before
443
  the browser processes the remote track event.
444
- Verify that recording failure never fails the call.
445
- Keep typed chat available during every voice failure.
446
- Verify barge-in, interruption, session end, tab destruction, and track cleanup.
447
- Verify typed input during a live voice call without ending that call or
448
  starting a competing typed assistant response.
449
- Verify disclosure before microphone access and a visible indicator only while
450
  recording runs.
451
- Verify chunk sequencing, size limits, truncation, late-chunk grace, encryption,
452
  retention, operator playback, export metadata, and deletion.
453
454
### Machines and delegated work
455
456
- Keep pairing and claim secrets out of logs, screenshots, and test output.
457
- Require one-time claim, owner approval, token replay refusal, and revocation.
458
- Bind every job to an owner, conversation, machine, authority set, and budget.
459
- Verify cancellation, worker restart, duplicate delivery, and terminal report
460
  persistence.
461
- Run only harmless staging jobs against disposable repositories and machines.
462
463
**Exit criteria:** Every asynchronous subsystem has tested interruption,
464
recovery, idempotency, ownership, and bounded-failure behavior.
465
466
## Gate 9: Harden the forge build lane
467
468
Do not enable staging hot loading while the current v0 build and load protocol
469
can accept partial fleet success.
470
471
### Replace the sidecar queue protocol
472
473
- Give every build a unique build ID. Do not key work only by SHA.
474
- Use JSON or another non-executable structured format.
475
- Write requests and responses through atomic temporary-file renames.
476
- Validate repository, SHA, request version, size, and allowed fields.
477
- Pass credentials through a protected file descriptor, mounted secret, or
478
  workload identity. Do not place credentials in repository URLs.
479
- Bound compiler output and preserve the full output only in an operator-owned
480
  artifact with defined retention.
481
- Recover or expire abandoned jobs without confusing their responses with a
482
  retry.
483
484
### Make artifacts immutable and verifiable
485
486
- Build the exact pushed commit in an isolated production toolchain.
487
- Record Elixir, OTP, application, dependency-lock, source, and baseline
488
  identities.
489
- Compare against the current live target's immutable manifest, not a sidecar's
490
  last warm build.
491
- Detect module additions, changes, and deletions.
492
- Normalize BEAM files before hashing and record an artifact SHA-256 digest.
493
- Store the artifact by digest in durable staging storage.
494
- Verify the digest, tar bounds, module count, entry names, BEAM module identity,
495
  and declared manifest before creating module atoms or loading code.
496
- Route deletions, NIF changes, application changes, dependencies, assets,
497
  configuration, ERTS, and OTP changes away from direct loading.
498
499
**Exit criteria:** A build can be reproduced and independently verified from
500
its pushed commit and immutable receipt, and malformed artifacts fail before
501
loading any module.
502
503
## Gate 10: Make fleet deployment transactional
504
505
Replace one-way remote loading with prepare, apply, verify, commit, and rollback.
506
507
1. Snapshot the expected healthy node set.
508
2. Verify the artifact and capture exact prior object code on every node.
509
3. Return an expiring deployment token from every prepared node.
510
4. Apply and smoke-test one canary.
511
5. Apply to the remaining prepared nodes.
512
6. Verify module identities, application revision, readiness, and expected node
513
   membership everywhere.
514
7. Commit only when every expected node reports success.
515
8. Restore every node that applied the candidate if any node fails or times
516
   out.
517
9. Verify the restored revision before recording `reverted`.
518
10. Remove a divergent node from readiness if rollback cannot restore it.
519
520
Never advance a target to `live` merely because the local canary passed. A
521
remote `error`, timeout, missing node, unexpected node, or failed verification
522
must block `live`.
523
524
Make boot convergence part of readiness:
525
526
- Fetch the current live artifact from durable storage on an empty node.
527
- Verify the same digest and manifest used during promotion.
528
- Keep readiness false when image code differs from the live target.
529
- Retry with bounded backoff.
530
- Retain the current and immediate rollback artifacts locally.
531
- Report a content-free convergence state on `/status`.
532
533
**Exit criteria:** Three-node tests prove consistent success, exact rollback,
534
timeout behavior, node replacement, cold-cache convergence, and refusal to
535
serve divergent code.
536
537
## Gate 11: Complete relup and rolling replacement
538
539
Direct loading is only one deployment class. Implement and test the two required
540
fallbacks before broadening the direct-load allowlist.
541
542
### Relup lane
543
544
- Use versioned state structs for long-lived processes that must survive an
545
  upgrade.
546
- Add tested `code_change/3` callbacks for every supported state transition.
547
- Build forward and reverse relups from explicit release versions.
548
- Stage, check, unpack, install, verify, and make permanent one node at a time.
549
- Reverse the relup when a health check fails.
550
- Re-stage consumed release artifacts after an interrupted install.
551
- Prove that a stateful process keeps its PID and data through upgrade,
552
  downgrade, and re-upgrade.
553
554
### Rolling replacement lane
555
556
- Build an immutable image identified by digest.
557
- Require a complete local gate receipt for the exact SHA.
558
- Drain one node, verify remaining capacity and quorum, replace it, and wait for
559
  membership and readiness before continuing.
560
- Abort before replacing another node when one node fails to rejoin.
561
- Keep database migrations additive while old and new revisions overlap.
562
- Contract schemas only in a later release after rollback is no longer needed.
563
564
### Owned release gate
565
566
Add `ops/ci/gate.sh` and `.githooks/pre-push` on owned infrastructure. The gate
567
should run:
568
569
1. Compile with warnings as errors.
570
2. `mix precommit`.
571
3. The separate cluster suite.
572
4. JavaScript tests.
573
5. Direct-load transaction tests.
574
6. Relup, reverse-relup, version-chain, and kill-during-install proofs.
575
7. Rolling drain and replacement proofs.
576
8. Documentation, invariant, secret, CSS, and icon contract checks.
577
9. A release build and startup smoke test.
578
10. A content-free gate receipt bound to the exact SHA.
579
580
**Exit criteria:** Every supported change has a safe deployment class, a tested
581
rollback or recovery path, and an exact-SHA gate receipt.
582
583
## Gate 12: Build an isolated staging environment
584
585
The existing Cloud Run staging service can validate the web application, OAuth,
586
LiveView, chat, memory, provider, and voice behavior. It cannot by itself prove
587
three-node BEAM hot loading, stable node identity, relup installation, or
588
node-by-node rolling replacement.
589
590
Use two staging lanes until the intended fleet replaces the web-only lane:
591
592
### Web acceptance lane
593
594
Use an isolated staging hostname such as `stage.openagents.com` for browser and
595
API acceptance. It needs:
596
597
- A staging-only GitHub OAuth application and callback.
598
- A staging-only database and database role.
599
- Staging-only OpenAI, encryption, forge, machine, and recording credentials.
600
- Secure cookies, HTTPS, WebSocket origin validation, CSP, and microphone policy.
601
- A reset feature enabled only for staging test accounts.
602
- Revision labels and log access that identify the exact deployment under test.
603
604
If Cloud Run remains temporarily, hard-reload every browser tab after a deploy.
605
An open LiveView socket can remain pinned to a draining old revision and produce
606
false regression results.
607
608
### Distributed deployment lane
609
610
Create a separate three-node staging fleet using stable instances or stateful
611
pods that support:
612
613
- Stable BEAM node names and private distribution.
614
- Private service discovery and expected membership.
615
- PostgreSQL connectivity and migration locks.
616
- Durable artifact and WAL storage.
617
- Node-local artifact caches.
618
- Build sidecar queues isolated from serving containers.
619
- Readiness removal and node drain.
620
- One-node-at-a-time replacement.
621
- Intentional Ra data storage and quorum behavior.
622
623
Match the eventual production topology closely enough that a staging relup or
624
rolling drill proves the same mechanism. Do not claim Cloud Run revision rollout
625
as evidence for an OTP relup.
626
627
### Staging isolation requirements
628
629
- Use a separate Google Cloud project or a strictly isolated staging boundary.
630
- Use staging-specific service accounts with minimum permissions.
631
- Use separate Secret Manager secrets, database, buckets, DNS records, OAuth
632
  client, and machine tokens.
633
- Deny access to production secrets and production databases.
634
- Mark every staging banner, status response, log entry, and receipt as staging.
635
- Take a database snapshot before migration and destructive data-rights drills.
636
- Define one command that removes disposable machines, repositories, recordings,
637
  and test accounts after the run.
638
639
**Exit criteria:** Staging can test both the user-facing product and the complete
640
distributed deployment mechanism without touching production state.
641
642
## Gate 13: Deploy to staging reproducibly
643
644
Use this sequence for every staging candidate:
645
646
1. Select an exact clean Git SHA.
647
2. Require its local gate receipt.
648
3. Build and retain the image, release, SBOM, build manifest, and artifact
649
   digests.
650
4. Restore a recent sanitized staging snapshot into a disposable rehearsal
651
   database and run every migration.
652
5. Run startup, rollback-compatible schema, and data-integrity checks against
653
   the rehearsal database.
654
6. Snapshot the actual staging database.
655
7. Deploy the candidate to the web acceptance lane with high-risk features
656
   disabled.
657
8. Confirm migration completion, `/healthz`, `/status`, database connectivity,
658
   LiveView connection, and revision identity.
659
9. Hard-reload persistent browser sessions so they connect to the new revision.
660
10. Enable one gated subsystem at a time and run its regression group.
661
11. Deploy the same candidate to the distributed lane.
662
12. Run direct-load, rollback, boot-convergence, relup, and rolling-replacement
663
   drills.
664
13. Collect sanitized logs, database truth checks, receipts, screenshots, and
665
   timing evidence.
666
14. Roll back staging if any blocking check fails.
667
668
Do not combine an application change, schema contraction, infrastructure
669
change, and first-time feature enablement in one staging candidate.
670
671
**Exit criteria:** Another operator can repeat the deploy from the recorded SHA
672
and obtain the same revision, schema, configuration posture, and checks.
673
674
## Gate 14: Run the staging regression matrix
675
676
Record every case as passed, failed, blocked, or not applicable. A retry does not
677
erase the first failure; record both attempts and explain the result.
678
679
### Public and browser surfaces
680
681
- `/healthz`, `/status`, `/api/status`, and `/favicon.ico` return their bounded
682
  expected responses.
683
- `/`, `/leaderboard`, `/changelog`, `/docs`, `/components`, and configured
684
  public forge pages render without an authenticated session where intended.
685
- Hidden, private, operator, and unconfigured forge surfaces do not disclose
686
  their existence.
687
- LiveView reconnects after a transient network interruption.
688
- CSP, cookie, origin, image, and microphone policies match the architecture.
689
- Phone, tablet, desktop, keyboard-only, reduced-motion, and screen-reader
690
  checks pass for critical flows.
691
692
### Authentication and account state
693
694
- GitHub OAuth state, PKCE, attempt expiry, replay refusal, callback errors, and
695
  banned-user behavior work.
696
- The persistent staging browser session remains logged in. Do not log it out
697
  when a user-controlled GitHub login would be required to restore it.
698
- Session renewal, logout, and concurrent-browser account continuity work.
699
- GitHub token storage or disposal matches the chosen contract.
700
- Account export, reset, and deletion operate only on the authenticated owner.
701
702
### Typed chat and Markdown
703
704
- A first conversation creates one greeting and one canonical conversation.
705
- Typed messages stream, persist, reload, paginate, cancel, fail, and recover
706
  correctly.
707
- A reset through `#reset-conversation-form` removes messages and memory for the
708
  staging account and returns to one greeting.
709
- Markdown renders supported structures and refuses raw HTML, unsafe URLs,
710
  script content, oversized input, and malformed nesting.
711
- Tool activity uses bounded persisted projections and never displays provider
712
  IDs or secrets.
713
- Rate, size, continuation, and tool budgets fail honestly.
714
715
### Memory and data rights
716
717
- Remember, list, search, correct, forget, export, reset, and delete flows pass.
718
- Activity chips and memory panels reflect PostgreSQL state after reload.
719
- Cross-account and cross-conversation reads fail.
720
- Snapshot fences exclude writes made after capture.
721
- Semantic failure returns the documented lexical fallback.
722
- A direct staging database query confirms the durable result when UI and cache
723
  behavior are ambiguous.
724
725
### Voice and recording
726
727
- `#voice-start`, `#voice-status`, and `#voice-end` drive a clean call lifecycle.
728
- The fake-media harness observes listening, Sarah speaking, barge-in,
729
  interruption, and clean end.
730
- `POST /voice/calls` returns `201` and the final delete returns `204` on a good
731
  run.
732
- Typed input during an active voice call persists without ending voice or
733
  starting a competing typed assistant response.
734
- The next spoken response can use the injected typed content.
735
- Voice tool calls complete within their budgets.
736
- Reloaded chat shows durable voice transcript items and interruption markers.
737
- Recording disclosure appears before microphone access.
738
- Recording chunks and completion reach the server with the correct generation.
739
- The operator can play the assembled recording, and unauthorized users cannot.
740
- Channel layout, encryption, truncation, retention, export metadata, and delete
741
  behavior match the invariant.
742
- A failed or unsupported recorder leaves the live call and typed chat usable.
743
744
### Leaderboard and administrative surfaces
745
746
- Anonymous leaderboard rows expose only the published entry fields.
747
- Typed and voice usage invalidates and refreshes the board without a database
748
  query per viewer.
749
- Operator allowlisting uses immutable GitHub IDs.
750
- Unauthorized `/admin` and `/admin/forge` access is indistinguishable from the
751
  documented unauthenticated path.
752
- Promotion accepts only a pushed commit and writes an immutable operator
753
  receipt.
754
- Administrative pages expose no transcript, prompt, credential, or private
755
  memory content beyond their explicit contract.
756
757
### Issues and Projects
758
759
- Public reads and authenticated writes follow the chosen route policy.
760
- Issue, comment, label, milestone, assignee, project, field, and item endpoints
761
  return the documented statuses and shapes.
762
- Browser LiveViews mount, show empty and populated states, validate forms, and
763
  perform one meaningful interaction each.
764
- Repository A cannot read, update, label, assign, or add an issue from
765
  Repository B.
766
- Issue and milestone numbers can repeat safely in different repositories.
767
- Pagination, filters, malformed IDs, missing rows, oversized input, and rate
768
  limits behave consistently.
769
770
### Machines and delegated work
771
772
- Public pairing creation, one-time claim, owner approval, inventory, offline
773
  and online state, and revoke work.
774
- Token replay fails after claim and after revoke.
775
- A harmless real staging coding-agent job appears live, reaches a terminal
776
  state, and writes its report into the conversation.
777
- Cancellation and worker restart preserve committed evidence and never execute
778
  a step twice.
779
- Cleanup removes the ephemeral controller home and disposable project.
780
781
### Forge and deployment
782
783
- Clone, fetch, and push work against the staging forge.
784
- WAL and mirror receipts match the pushed refs.
785
- A valid allowlisted change passes build, canary, fleet transaction, and live
786
  receipt.
787
- An off-allowlist module refuses the complete direct-load candidate.
788
- Corrupt digest, manifest mismatch, module deletion, oversized tar, and invalid
789
  module identity fail before loading.
790
- Remote timeout and remote load failure restore every affected node.
791
- A cold replacement node fetches the durable artifact and becomes ready only
792
  after convergence.
793
- A stateful relup preserves PID and state through upgrade, downgrade, and
794
  re-upgrade.
795
- Killing a node during relup installation returns it on the prior permanent
796
  release and permits a clean retry.
797
- A structural change uses rolling replacement and removes only one node from
798
  readiness at a time.
799
- Failed replacement stops the sequence before another node drains.
800
801
### Logs and operational truth
802
803
- Query logs against the exact new revision and the exact test window.
804
- Inspect severity-based errors and application `[error]` text entries.
805
- Separate deploy-overlap database connection noise from candidate regressions.
806
- Correlate failures with request IDs and receipt IDs without logging content.
807
- Use direct database queries to distinguish projection failure from persistence
808
  failure.
809
- Confirm that logs contain no OAuth codes, tokens, prompts, messages,
810
  transcripts, memory claims, tool payloads, SDP, audio, or credentials.
811
812
**Exit criteria:** Every applicable case passes on the same candidate SHA, and
813
every failed first attempt has a documented cause and successful corrective
814
verification.
815
816
## Gate 15: Run failure injection and soak staging
817
818
After functional regression passes, test the system under controlled failure.
819
820
Inject these failures one at a time:
821
822
- Provider timeout, malformed event, and stream closure without completion.
823
- PostgreSQL restart and temporary connection exhaustion.
824
- PubSub interruption and LiveView reconnect.
825
- Turn, voice, work, semantic worker, builder, and deployer process termination.
826
- Machine disconnect during a job.
827
- Artifact-store unavailability and corrupt cache.
828
- One unreachable fleet node.
829
- Node membership change during a deployment.
830
- Build sidecar crash and stale response.
831
- Browser navigation and tab destruction during microphone use.
832
- Recording upload failure and late final chunk.
833
834
After failure injection, run a staging soak:
835
836
- Keep the web and distributed staging lanes active for at least 48 hours.
837
- Exercise scheduled typed, memory, voice, tracker, Git, and status canaries.
838
- Watch database connections, queue depth, mailbox growth, process count,
839
  memory, CPU, restart count, artifact cache, Ra state, and node convergence.
840
- Investigate every crash, unexplained retry, stale active row, divergent node,
841
  leaked process, and content-bearing log entry.
842
- Repeat the full smoke group after the soak without redeploying.
843
844
**Exit criteria:** Staging survives controlled failures and the soak without
845
data loss, authority expansion, fleet divergence, secret leakage, or unexplained
846
error accumulation.
847
848
## Required staging evidence
849
850
Store one staging report per candidate. Include:
851
852
- Git SHA, branch, image digest, release version, artifact digests, and SBOM.
853
- Database migration versions and rehearsal result.
854
- Redacted configuration readiness report.
855
- Default and cluster test counts, coverage summary, and JavaScript results.
856
- Staging service revision and distributed node release identities.
857
- A pass, fail, blocked, or not-applicable result for every regression group.
858
- Sanitized screenshots or recordings for critical UI and voice flows.
859
- Sanitized log queries and direct database truth checks.
860
- Forge build, deployment, rollback, relup, and rolling receipts.
861
- Failure-injection and soak timelines.
862
- Every known issue, its owner, severity, and disposition.
863
864
Never store session cookies, OAuth codes, access tokens, database passwords,
865
machine tokens, provider keys, raw prompts, transcripts, memory values, or audio
866
in the report.
867
868
## Production hold conditions
869
870
Production remains blocked while any of these conditions is true:
871
872
- Documentation and `INVARIANTS.md` disagree with the implementation.
873
- A current invariant points to missing evidence.
874
- Any test is silently skipped or a cluster test is not run.
875
- A critical route lacks an explicit authority class.
876
- Repository data is not scoped and constrained by repository ID.
877
- GitHub token retention is undocumented or cannot be rotated and revoked.
878
- A recovery worker lacks direct tests.
879
- The forge can mark a partial fleet deployment live.
880
- Artifact identity, digest, rollback, boot convergence, relup, or rolling
881
  replacement lacks staging proof.
882
- Staging depends on production credentials or data.
883
- A staging regression, failure-injection case, or soak issue remains
884
  unexplained.
885
- Logs contain secrets or private product content.
886
- The candidate did not complete the full staging matrix on one exact SHA.
887
888
Passing staging does not automatically authorize production. It only creates a
889
reviewable production-readiness candidate for a later plan and explicit owner
890
decision.
891
892
## Suggested commit sequence
893
894
Keep commits independently reviewable and run the relevant local gates before
895
each handoff.
896
897
| Order | Commit | Required evidence |
898
| --- | --- | --- |
899
| 1 | Add integrated architecture decisions and repair the README | Documentation checks pass |
900
| 2 | Reconcile plans, component docs, and invariant evidence | No broken evidence links or duplicate invariant IDs |
901
| 3 | Rename generic Sarah infrastructure and configure `openagents.com` targets | Allowed-reference check passes |
902
| 4 | Centralize and validate runtime configuration | Invalid staging configurations fail before traffic |
903
| 5 | Resolve Markdown, UI, icon, dependency, and license consolidation | Parser security and component contract tests pass |
904
| 6 | Resolve GitHub token policy and classify route authority | Auth, CSRF, replay, and secret-redaction tests pass |
905
| 7 | Add repository entities and tenant-scoped tracker data | Cross-repository isolation tests pass |
906
| 8 | Close chat, recovery, memory, voice, work, and machine hardening gaps | Async failure and recovery tests pass |
907
| 9 | Replace the forge build queue and add immutable artifact manifests | Build reproducibility and corruption tests pass |
908
| 10 | Add transactional fleet deployment and readiness-bound boot convergence | Three-node rollback and cold-boot tests pass |
909
| 11 | Complete relup, reverse-relup, and rolling replacement | Upgrade and replacement drills pass |
910
| 12 | Add the owned release gate and content-free receipts | Exact-SHA refusal and full local gate pass |
911
| 13 | Provision isolated web and distributed staging lanes | Isolation and configuration review pass |
912
| 14 | Add staging harnesses and the evidence report template | Regression harness dry run passes |
913
| 15 | Deploy one staging candidate and complete the full matrix | Staging report is complete |
914
| 16 | Complete failure injection and the 48-hour soak | No unexplained blocking issues remain |
915
916
## Final staging readiness checklist
917
918
- [ ] The repository has one accurate architecture narrative.
919
- [ ] Every remaining Sarah reference is intentional and specific.
920
- [ ] All documentation links and invariant evidence resolve.
921
- [ ] The application has one Markdown parser, component system, and icon path.
922
- [ ] Runtime configuration is typed, redacted, and staging-specific.
923
- [ ] Every route has an explicit authority class.
924
- [ ] GitHub token behavior matches code, UI disclosure, and data rights.
925
- [ ] Issues and Projects are scoped by repository in code and PostgreSQL.
926
- [ ] Every asynchronous recovery path has direct tests.
927
- [ ] Voice recording starts only after generation admission.
928
- [ ] Build requests are structured, unique, bounded, and non-executable.
929
- [ ] Artifacts are immutable, digested, manifest-checked, and durably stored.
930
- [ ] Fleet deployment is transactional and rolls back every affected node.
931
- [ ] Boot convergence controls readiness.
932
- [ ] Relup and rolling replacement pass their staging drills.
933
- [ ] Owned local gates produce exact-SHA receipts.
934
- [ ] Web and distributed staging are isolated from production.
935
- [ ] The complete regression matrix passes on one SHA.
936
- [ ] Failure injection and the 48-hour soak pass.
937
- [ ] The staging evidence report contains no secrets or private content.
938
- [ ] No production action has occurred.

This page updates live while a promote is in flight · changelog