Skip to content

feat: implement OpenBot collaboration backlog - #1

Merged
Blackman99 merged 237 commits into
mainfrom
feat/openbot-collaboration-system
Sep 7, 2026
Merged

feat: implement OpenBot collaboration backlog#1
Blackman99 merged 237 commits into
mainfrom
feat/openbot-collaboration-system

Conversation

@Blackman99

@Blackman99 Blackman99 commented Sep 5, 2026

Copy link
Copy Markdown
Owner

OpenBot is a self-hosted Bot collaboration system supporting configurable OpenAI Chat Completions, OpenAI Responses and Anthropic Messages-compatible endpoints.

This draft implements the 67-ticket, 401-criterion backlog. 29 tickets are now fully implemented, independently reviewed and verified. All original acceptance texts are preserved. Cloud computers are excluded.

The latest feature cancels queued and running Task trees with current authorization, aborts active or silent provider requests, preserves interrupted output across stream reclamation and reload, and rejects late answers. Stable command receipts recover lost responses. The subsequent narrow CI correction handles migration 0023 on databases containing queued Tasks and preserves the typed timeout after a blocked stream write. Both passed independent Spec and Standards reviews.

Current publication ad96195402ea48e0ceabf795d945db1d46cd2289 matches accepted local c7dcdcb6b3848a89407c76664869f8290d0fe9c5, tree 28161d0dd57032184fec224c3a838ddf216c87e6, verified by fetch and pinned diff. The actual PR checkout 69164ce0b50efbafe5e12862cb951b6476c1a960 has that exact tree.

Verify 33968122031 completed 2026-09-05 13:10:21–13:16:39 UTC, all 16 jobs successful:

  • Complete code gate: 1,505 nonbrowser tests, 60 ordinary browser journeys and one signed OIDC journey, plus format, types and builds.
  • 271 distinct native PostgreSQL cases, including all 18 cancellation cases, all 40 Task cases and all 40 stream cases.
  • 14 real storage cases.
  • All three Compose flows: base fresh/upgrade/permissions/application/outage checks, separate Task/stream workers, and separate cancellation worker/provider with seed/cancel/reloaded stages.

COL-07-E1 is closed. The repository checkpoint is updated locally and will accompany the next accepted publication. Discovery skips are excluded; no local PostgreSQL or Docker execution is claimed.

The preceding failed Verify 33967009969 remains recorded: 13/16 jobs passed, with a populated-upgrade deferred-trigger error and two Task/stream deadline error boundaries. The narrow correction evidence records the actual failure, a witnessed regression and focused checks. The subsequent successful run above verifies the corrected source.

Previously completed features include authentication/OIDC, workspaces and roles, personal/shared model connections and capabilities, Bot identity/ACL/avatar/versioning/copy/lifecycle, groups and history grants, durable conversations and separate-worker Tasks, ordered live replies and reconnect, deterministic Bot routing, immutable manual retry history, scoped group memory, private attachments with tracked purge, API tokens and the public Bot API.

COL-10 automatic retries/fallbacks, MEM-02 Bot-private memory and KNW-01 cited file knowledge continue in isolated worktrees; reviewed precursor slices do not close those full tickets. 38 tickets remain: 3 in progress and 35 awaiting their actual dependencies. Main remains ecc586a8d3b528728af2308e247c4c3c4fb75ffa; PR1 remains draft while the complete backlog and release gates are implemented.

Record successful code, PostgreSQL and Compose evidence for FND-01, AUTH-01 and WS-01; resume the approved dependency graph from WS-02 and PROV-01.

Source local commit: 027b6afa2dc0a03313d2357a2710ca88e1fe2501
Source tree: 81127ed
@changeset-bot

changeset-bot Bot commented Sep 5, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: e498a40

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

Blackman99 and others added 28 commits September 5, 2026 10:59
Add owner-scoped connection settings and APIs, live text/action probes, authenticated credential encryption, destination policy with pinned DNS, bounded redacted evidence, optimistic writes, audits and runtime database grants.

Verified locally: formatting/types, 145 unit/integration tests, 6 browser scenarios, API/Web builds, and independent standards/spec reviews with regression fixes. Actual PostgreSQL/Compose evidence remains tracked as PROV-01-E1 pending this PR's CI.

Source local commit: f5a5c71f01860a79dfeb5b0cf8f29cb099b95ccd
Source tree: aed2091
… checks

Add invitation management and joining for new and existing users, with atomic token consumption, audits and immediate authority checks. Fix the infrastructure PostgreSQL dependency found by CI.

Verified locally: 186 unit/integration tests, 7 browser scenarios, formatting, types and production builds. Real PostgreSQL and Compose gates remain pending on this revision.

Local commit: 014320d5261af8cf0c5e0a4158d135459936794d
Verified Git tree: bb883a5
…tion

Support persisted Responses selection, shared text/action events and normalized endpoint errors. Preserve cancellation and guarded transport, separate generation limits from diagnostic retention, and accept valid SSE line endings.

Verified: 213 unit/integration tests, 7 browser scenarios, formatting, types and production builds. Independent standards and specification reviews passed. PostgreSQL/Compose checks must pass on this revision.

Local commit: 42ff213a6196f2ea186b78a837226e6e70adb4c5
Verified Git tree: 57fbb8b
Add explicit Anthropic version and credential settings, live text and tool events, usage and stop normalization, and the shared guarded transport. Fix protocol-specific native form validation with witnessed browser regressions.

Verified: 249 unit/integration tests, 8 browser scenarios, formatting, types and builds. Independent standards and specification reviews passed. Actual PostgreSQL and Compose must pass on this revision.

Local commit: 980af1309f325061ca451621e569c18dd6668c6c
Verified Git tree: 67851a9
Add scoped member listing, invitation provenance, owner/admin authority and transactional last-owner protection. Preserve account and session identity after removal while denying former-workspace access. Fix member and invitation BFF DELETE formatting and response-body deadlines.

Verified: 284 unit/integration tests, 9 browser scenarios, formatting, types and builds; independent standards/spec reviews and deployment-smoke review passed. Real PostgreSQL and Compose gates remain pending on this revision.

Local commit: 2baa1882dafddb2d7f30646bc351976eca24c56c
Verified Git tree: 50dbae9
Support signed Authorization Code flows with PKCE, browser-bound single-use transactions, issuer-subject identities, explicit account linking and invitation-only registration. Add dedicated real IdP browser coverage and isolated restricted-role PostgreSQL CI.

Verified: 352 unit/integration tests, 9 ordinary browser scenarios and one signed-IdP journey, formatting, types and builds. Independent standards/spec and integration-delta reviews passed. Workspace member PostgreSQL/Compose evidence is closed; OIDC external gates remain pending.

Local commit: 8081ae9c77be585238d7c84247c8fd1d8fbe1600
Verified Git tree: f21c348
Separate workspace usage from credential administration, preserving personal credential encryption and canonical workspace identities. Recheck current authority and revisions before each probe generation and result write, retain disabled connection identities, and expose safe role-specific settings across all three protocols.

Verified: 400 unit/integration tests, 10 ordinary browser scenarios and one signed-IdP journey, formatting, types and builds. Independent standards/spec and deployment-assertion reviews passed. Shared-provider PostgreSQL/Compose gates remain pending; OIDC runtime evidence is closed.

Local commit: 1e1f6643d4a410667fc460c658bf1fc004143b91
Verified Git tree: 4fb37d3
Add group creation, metadata, discovery and owner/admin/member management. Require current workspace membership together with explicit group authorization, preserve historical authors and retained grants, and protect the last eligible owner. Add canonical UUID handling and genuine historical-schema migration fixtures.

Verified: 445 unit/integration tests, 11 ordinary browser scenarios and one signed-IdP journey, formatting, types and builds. Independent standards/spec and new Compose lifecycle reviews passed. Shared-provider runtime gate is closed; group PostgreSQL/Compose gates remain pending.

Local commit: 141369819bf53a2102ba2ccc3be06b55df0ec344
Verified Git tree: 2d959c6
…iness

Add one-time token creation, hash-only storage, redacted management, public identity, fixed scopes, current-membership checks and permanent member-removal revocation. Preserve workspace-first locking and use fresh post-lock timestamps. Canonicalize member-removal audit references.

Fix PostgreSQL readiness probes to require authenticated target-database TCP queries before migrations and legacy-volume seeding; cover temporary initialization, unavailable databases and seed-once behavior.

Verified combined feature: 478 unit/integration tests, 12 ordinary browser scenarios plus one signed-IdP journey, types, formatting and builds. Follow-up audit/readiness changes: 31 focused tests, types, formatting and shell syntax checks. Independent feature and narrow-fix reviews passed. Actual API-token and group deployment gates remain pending.

Local commit: e0213a5e7dd4997868d0fa07c6bbed98d00d0e43
Verified Git tree: fb90774
…k policies

Add explicit capability evidence, attributable re-probes, justified manual overrides with persistent active/stale badges, target generation invalidation, optimistic revisions and safe audit snapshots. Validate same-scope fallback graphs atomically, preview deterministic current eligibility, and expose narrow transaction-scoped model binding admission.

Add personal/workspace capability pages with permission-aware controls and explicit unavailable selections. Correct group deployment smoke to verify invited roles through the membership API, retaining all authorization/audit checks and safe failure diagnostics.

Verified: 539 unit/integration tests, 14 ordinary browser scenarios plus one real signed-IdP journey, formatting, types with zero Web warnings, both builds and shell syntax. Independent standards/spec and integration-delta reviews passed. Group, API-token and capability PostgreSQL/Compose gates remain pending the full deployment retry.

Local commit: f3d3671c2b3034c1d4d4e3224789643505ff7413
Verified Git tree: 514ec8f
Verified local snapshot 47553b1e5331aeaa869d44e96537b38d53d9fd2b; both review axes clean, 594 tests and 16 browser journeys passed. Add isolated PostgreSQL Bot validation and ordered Compose privileges.
Publish the independently reviewed BOT-04 integration with 651 passing tests and 18 browser scenarios. Keep native PostgreSQL and Compose validation as an explicit pending CI gate.
Publish independently reviewed BOT-02 integration: 704 tests and 19 browser scenarios pass. Preserve immutable historical references and add real PostgreSQL, private S3, and deployed image-runtime CI gates.
Integrate independently reviewed Bot copy and archive/restore/soft-delete flows. Preserve private configuration access, fixed recovery grace, actual-user model admission, immutable versions and retained avatar references.

Accepted local source 6c98be0db757408d0c8a764197becdf5627f4f8f. Full integration verification: 969 unit/integration tests, 32 ordinary browser scenarios, one signed-OIDC journey, lint, typecheck and builds. Follow-up Compose expectation checks are scoped and native PostgreSQL/Compose evidence remains pending.
Publish reviewed API-02 integration and the isolated S3 case budget correction. Record 24 implemented tickets, 22 fully complete, with native public-Bot and large-file S3 gates explicit.

Accepted local snapshot: e51fafe4f5de1e5f0f5b8e1f91dbbc93201143c8
Exact tree: 2bb6bd7
…nd scoped group memory

Accepted local snapshot b5a744f7e8637168832f054e524f80af671a65be. Both independent review axes CLEAN; full verify: 1,453 unit/integration, 53 browser, one OIDC. Four actual PostgreSQL/Compose gates remain pending. Preserve all 67 tickets and 401 acceptance texts.
…utput

Accepted local snapshot d0c8c34291c256fb78222322da37b8a2b61c591b.
Full verify passed: 1504 nonbrowser, 60 browser and one OIDC tests.
Add dedicated native cancellation and separate-worker Compose gates.
Accepted local snapshot c7dcdcb6b3848a89407c76664869f8290d0fe9c5.
Temporarily validate the named backfill constraint before DDL; retain schema and guards.
Check expired progress before durable partial writes and retain native rollback checks.
…backs

Tighten automatic-retry eligibility to the closed COL-10 allowlist and add optional versioned retryPolicy plus same-scope fallbackBindings without rewriting historical Bot JSON.

Co-authored-by: Dongsheng <Blackman99@users.noreply.github.com>
Schedule automatic same-Task continuations only for the closed transient
failure allowlist, persist a single notBefore, and admit at most one
successor without fabricating a retry command or registering migration 0024.

Co-authored-by: Dongsheng <Blackman99@users.noreply.github.com>
Show previous and next protocol plus model, the closed failure reason,
and whether the next model is only planned or already admitted, without
connection, endpoint or credential fields.

Co-authored-by: Dongsheng <Blackman99@users.noreply.github.com>
Complete remaining ROUT-01 AC2 public API mutations with groups:write,
workspace-authority locking before routine FOR UPDATE, and token admit
recheck. UI, trigger, recovery, uniqueness, and bot-deny ACs stay open.
Add ROUT-01 AC3/AC5 (and cohesive AC4) slice: migration
0052_routine_occurrences enforces UNIQUE(routine_id, occurrence_key),
RoutineExecutor claims due active routines on the task worker poll,
creates exactly one standard collaboration task with the routine budget,
links it for GET /v1/routines/:id, and recovers unexpired due work after
restart without duplication. AC2 UI and AC6 bot-deny remain open.
Pin the Compose fresh-volume migration ledger assertion to
0052_routine_occurrences after the ROUT-01 occurrence uniqueness cut.
Complete ROUT-01 leftover ACs: session group routines API plus web
create/edit/pause/resume/cancel pages, and explicit worker denial of bot
create_routine and budget/frequency edit_routine collaboration actions.
Leave ticket checkboxes unchecked for Tester.
Close ROUT-01 after tip 5ba2a2a, green Verify 34035394210, and
Tester 6/6 PASS. Mark ROUT-02 ready-for-agent and DEPLOY-01
in-progress for the single-host Compose baseline.
DEPLOY-01 first TDD slice: OPENBOT_TELEMETRY defaults to false in
.env.example and Compose, API/worker construct only a local no-op
recorder, and idle API/worker smokes make no unnecessary outbound
HTTP. Contract tests also lock undeclared postgres/worker host ports.
DEPLOY-01 second TDD slice for AC1–4: add a worker healthcheck, wait for
a healthy API before starting web, and lock migrate-once, named-volume
durability, and private-network model wiring in Compose/README contracts.
AC5–6 remain Tester-proven; AC1–4 stay unchecked until live stack proof.
…ions

Close ROUT-01 AC2's Web surface and AC6.

Session routes back a group Routines page: list, create, edit, pause,
resume and cancel reach the same RoutineService as /v1/routines, take the
trusted web origin on every write, and require current group content
authority. Schedules are entered as wall time in the routine's own IANA
zone, so the browser's zone never moves an execution; a wall time skipped
by a forward DST transition is refused before it reaches the API.

AC6 is enforced twice. BOT_COLLABORATION_TOOL_NAMES pins the four tools a
Bot is ever offered and offersRoutineTool keeps a routine capability out
of that set, while the worker's closed action whitelist fails any
routine-shaped action without touching the table. RoutineService also
takes a RoutineActor origin: a Bot-attributed create is refused, as is a
Bot-attributed edit that raises max_cost_micros, moves execute_at
earlier, or extends expires_at. A Bot-attributed reduction still works.

No new migration; the ledger tip stays 0052_routine_occurrences.

ROUT-01 is stamped complete-with-external-verification. ROUT-01-E1 opens
on REL-01 for the routine PostgreSQL cases under the restricted runtime
role and the routines.spec.ts browser scenario: this host runs neither,
because its bundled Chromium is a Linux binary and /bin/bash is 3.2. The
page was instead verified against the built app over HTTP, 27/27 checks.
Close ROUT-02.

Migration 0053_cron_routines adds routines.cron_expression, keeps kind
and the expression consistent, and extends routine_occurrences.outcome
with skipped_overlap. A cron routine reuses execute_at as its next
occurrence and advances it on every tick, so the published due-work query
and the 0052 occurrence-uniqueness guard are unchanged.

The scheduler walks candidate wall times rather than instants and
resolves each through the zone. That is what makes both daylight-saving
transitions correct: an hour a forward transition removes has no instant
and is stepped over, and an hour a backward transition repeats resolves
to exactly one instant, so no occurrence is duplicated. Cron treats
day-of-month and day-of-week as a union when both are narrowed.

A tick whose prior task is not completed, failed or cancelled records
skipped_overlap and creates nothing. A tick older than CRON_CATCH_UP_MS
records routine.missed, backfills nothing, and resumes at the first
occurrence after now. A routine with no occurrence left inside its window
disables itself and creates no later task.

Run history joins occurrences to their tasks, so completed, failed,
cancelled, expired and skipped_overlap stay distinct. The group Routines
page takes a schedule type, accepts a five-field expression, and shows
the next execution, the latest result, and a bounded run history.

A Bot-attributed cron change is refused with the other ROUT-01 AC6
escalations: relative frequency of two expressions is not decidable here.

ROUT-02 is stamped complete-with-external-verification. ROUT-02-E1 opens
on REL-01 for the cron PostgreSQL cases under the restricted runtime role
and the cron browser scenario, which this host cannot run. The page was
verified against the built app over HTTP: 28/28 one-time and 24/24 cron
checks, covering zone conversion, overlap, catch-up, history and cancel.
Close NOTIF-01.

Migration 0054_task_notifications stores one content-free pointer per
recipient: who, which workspace event, and the group, conversation and
task to open. No body, prompt, summary or title is copied, so an inbox
row cannot disclose content the reader's authorization would refuse.

Fan-out runs inside the producer's own transaction from
publishWorkspaceTaskEvent, so a notification never outlives a rolled-back
outcome and never lags the stream. UNIQUE (workspace_id, user_id,
source_sequence) with ON CONFLICT DO NOTHING makes a retried producer and
a replayed stream both idempotent. Cancellation and interim updates stay
stream-only: the inbox covers pending approvals, completion, failure and
exhausted budgets. A pending approval reaches every human member of the
group that can answer it; a terminal or budget outcome reaches the person
whose request was waiting.

Reads are scoped to the caller's own rows, and opening asks the API to
re-lock the referenced conversation before it returns a destination, so a
notification kept from a group the reader has left leads nowhere.

The Web inbox pages newest first from a createdAt|id cursor, marks one or
all read, and updates live: the browser opens an EventSource against the
web server, which forwards the session cookie to GET /v1/events, so no
API credential reaches the page. Each frame triggers a fetch of a private
JSON feed rather than trusting frame contents.

NOTIF-01 is stamped complete-with-external-verification. NOTIF-01-E1
opens on REL-01 for the notification PostgreSQL cases under the
restricted runtime role and the browser scenario, which this host cannot
run. The inbox was verified against the built app over HTTP: 24/24
checks, plus a separate 6/6 live-delivery check that emits an event after
the stream is already open.
Close NOTIF-02.

Migration 0055_mention_notifications adds conversation_event_mentions,
which keeps stable member ids beside the message.created event so a
renamed member is still the recipient; group_notification_settings, one
level per member per group; the mention inbox kind with a nullable
task_id and a message_id; and the message.mention workspace event type,
so an open inbox receives a mention live.

A mention names a current human member of the group by id. A removed
member, a stranger, or a Bot grant is refused before the message is
written, so a refused mention stores nothing and no notification can
carry group content to an unauthorized reader. The stream frame names
only the conversation, message and event; recipients are computed from
the stored mention rows inside the same transaction, and the author is
never notified about their own mention.

Fan-out reads membership and levels when the event is published, so a
change applies to the next event and a member the group removed receives
nothing new. all receives approvals, own-task outcomes and mentions;
mentions_only receives direct mentions only; muted receives nothing from
that group. Direct-Bot conversations have no group and are unaffected.
Bot mentions remain the explicit Lead grant on a task submission and
touch routing only.

The Web message form offers a member multi-select for group
conversations, the group page carries the member's own level, and the
inbox opens a mention at its message anchor.

NOTIF-02 is stamped complete-with-external-verification; NOTIF-02-E1
opens on REL-01 for the PostgreSQL cases under the restricted runtime
role and the browser scenario, which this host cannot run. DATA-01 is
now ready: both of its remaining blockers carry explicit REL-01 gates.
The flow was verified against the built app over HTTP, 21/21 checks.
@Blackman99
Blackman99 marked this pull request as ready for review September 6, 2026 15:30
Close DATA-01.

Migration 0056_workspace_exports stores one job row per export: status,
attempt, schema version, object id, size, SHA-256, file count, manifest,
error code and timestamps. Checks tie a completed export to an archive and
strip an expired one of both object and manifest.

The collector reads an explicit, workspace-scoped table allowlist and
passes every row through SECRET_COLUMN_PATTERN, so credential, session,
token, provider and key-material tables are never read and a
secret-bearing column in any allowlisted table is dropped. Attachments
are the only binary content, each read through the attachment store's
own bound. A dependency-free ustar writer puts manifest.json first, with
a size and SHA-256 for every file, and export.json names the schema
version and creation time.

The export worker runs on the task worker poll. Claiming is a queued to
running transition, completion is guarded by the running status, and an
orphaned second object is deleted rather than recorded, so repeated
completion cannot produce conflicting state. A failure keeps the row with
its error code and retry re-queues it as a fresh attempt. Archives are
deleted 24 hours after completion while outcome, size, checksum and
timestamps stay in the record and the workspace.export_* audit trail.

Only workspace owners and administrators can create, list, inspect,
retry or download; the Web settings page proxies the download so the
session cookie never reaches the browser.

DATA-01 is stamped complete-with-external-verification; DATA-01-E1 opens
on REL-01 for the PostgreSQL cases under the restricted runtime role, the
S3-compatible archive contract and the browser scenario, none of which
this host can run. DATA-02 is now ready. The flow was verified against
the built app over HTTP, 27/27 checks, including a parsed archive whose
manifest checksums all match and which carries no credential sentinel.
Close DATA-02.

Migration 0057_workspace_soft_deletion adds deleted_at, purge_after and
deleted_by_user_id to workspaces with a consistency check; no other table
changes, so content, attachments, memory and indexes stay untouched until
the grace period ends and a later purge runs.

Enforcement is central. lockWorkspaceAuthority, the authority every
group, Bot, task, member, token, event, notification and export path
takes, now selects only undeleted rows; the API-token authorization join
and the session event-stream admission require an undeleted workspace;
the session identity prefers live workspaces without locking a person
out; the workspace list hides a deleted workspace from everyone but its
owners; and a preHandler in buildApp refuses every workspace-scoped
session route except the workspace record and the deletion routes. A
member therefore loses the workspace at once through the UI, REST and SSE.

WorkspaceLifecycleService locks the workspace row for every transition
and writes conditionally, so repeated or concurrent deletes and restores
converge on one state and only the first records an audit. Owners
delete; owners or the instance administrator restore until purge_after,
after which grace_period_ended is returned. The grace period is
WORKSPACE_DELETION_GRACE_DAYS, default 30, in whole days.

The Web confirmation page offers the data export first, shows the exact
purge_after a deletion confirmed now would record, requires the typed
workspace name, and an owner still sees the deleted workspace flagged
with a way to restore it.

DATA-02 is stamped complete-with-external-verification; DATA-02-E1 opens
on REL-01 for the PostgreSQL cases under the restricted runtime role and
the browser scenario, which this host cannot run. DATA-03 is now ready.
The flow was verified against the built app over HTTP, 23/23 checks.
Close DATA-03, the expand phase of the purge.

Migration 0058_workspace_purge_manifest adds a versioned manifest whose
status becomes completed only when every step is recorded complete, and
one step row per target table with the handler, handler version,
estimated rows, deleted rows and outcome.

The handler registry lists eleven versioned relational handlers with
their tables in dependency order: notifications, routines, memory
metadata, knowledge rows, tasks and runs, group membership and grants,
messages, group rows, Bots, API tokens and the event stream. Every table
is scoped by a predicate derived from the workspace id, directly or
through a join to tasks, runs, conversations, groups, Bots, memories,
candidates, intents or requests, so a handler can never reach a
neighbouring workspace and running it twice deletes nothing new.

dryRun counts with the same predicates and writes nothing. execute
requires a workspace that is soft-deleted and past purge_after, rechecks
that before every step so a restore stops it, reuses the latest
unfinished manifest so a retry resumes after its completed steps, runs
each table step in its own transaction, records a failed step with its
error code, and never reports a partial run as complete. Nothing wires
execute to a schedule or a route: enabling the irreversible purge is
DATA-05, and object storage, caches and indexes are DATA-04.

DATA-03 is stamped complete-with-external-verification; DATA-03-E1 opens
on REL-01 for the native PostgreSQL run under real foreign keys, which
this host cannot execute. DATA-04 is now ready.
Close DATA-04, the migrate phase of the purge.

The DATA-03 manifest already records status, estimate, deleted rows and
failure code per step, so no migration is needed: the manifest version
moves to 2 and the registry gains object-backed steps. A step may name an
object column and kind; the service reads the matched rows, deletes each
object from the store for that kind with a key built from the workspace
being purged and never from the row, treats a missing object as already
deleted, and fails the step with a store_* code on any other store error,
all before the row DELETE in the same step transaction.

Handlers: attachments (attachment_objects.storage_id), avatars
(bot_avatar_references, then avatar_objects by id), exports
(workspace_exports.object_id) and connections (model_price_versions, then
workspace_model_connections, whose sealed_credentials carries the wrapped
key material), with the knowledge handler unchanged. This codebase keeps
no separate summary store, vector index or cache; its only search index
is the GIN full-text index on knowledge_chunks.text, which the knowledge
row deletion empties. A purge whose object step has no configured store
fails that step and deletes no rows.

Fault injection proves every deleted key names the purged workspace, a
mid-run store failure leaves completed deletions and the neighbour's
objects intact, and a retry finishes what remains.

DATA-04 is stamped complete-with-external-verification; DATA-04-E1 opens
on REL-01 for the S3-compatible store and native PostgreSQL runs this
host cannot execute. DATA-05 is now ready.
Close DATA-05, the contract phase of the purge.

Migration 0059_workspace_retention_and_receipts adds the instance_settings
singleton with workspace_retention_days (1 to 3650, seeded at 30) and
workspace_purge_receipts, whose only key is a SHA-256 fingerprint of the
workspace id. The instance administrator reads and changes the retention
period through GET/PATCH /api/v1/instance/settings and the /app/instance
page; every new deletion, and the confirmation page's preview, records
purge_after from it.

WorkspacePurgeService.finalize runs in one transaction: it re-locks the
workspace row, requires deleted_at set and purge_after passed, requires
the current-version manifest to be completed with no open step, aggregates
per-handler counts, removes the manifest, invitations, memberships and the
workspace row, and writes the receipt plus a workspace.purged audit that
names only the fingerprint, the timestamps, who requested it, the result
and the counts. Names, ids and content do not survive.

RetentionWorker runs on the task worker poll: one soft-deleted workspace
past its purge_after per tick, executing or resuming its manifest and
finalizing only on completion; a workspace restored in the meantime is
skipped by the eligibility checks inside the purge itself. Once purged, a
workspace cannot be listed, exported, restored or read through the API or
the event stream.

The end-to-end test seeds messages, tasks, routines, notifications,
memories, attachments, knowledge, avatars and provider connections in two
workspaces, runs the scheduler, and proves the target is reduced to its
receipt while the neighbour keeps every row and object.

DATA-05 is stamped complete-with-external-verification; DATA-05-E1 opens
on REL-01 for the native PostgreSQL and S3-compatible run this host cannot
execute.
Add compose-baseline.test.ts, which parses compose.yaml and asserts the
one-command layout: an internal data network with no PostgreSQL, migrate or
worker host ports, API and web ports bound to OPENBOT_BIND_ADDRESS (loopback by
default), a one-shot migrate service the API and worker depend on with
service_completed_successfully, health gates, postgres-data and object-data
volumes on both API and worker, the provider host/scheme/CIDR policy on both,
read-only hardened containers, and no telemetry configuration anywhere.

Add idle-outbound.test.ts, which instruments fetch, http.request and
https.request while the API answers its status probe and the worker polls an
empty queue, and asserts no outbound request is made.

Add docs/deployment.md covering start, required secret changes, network
exposure, persistence across restarts, private-network model endpoints and the
telemetry position; link it from the README and note it in .env.example.

Stamp DEPLOY-01 complete-with-external-verification with gate DEPLOY-01-E1 on
REL-01, unblock DEPLOY-02 and PWA-01, and update the backlog index and frontier.
Add schema-compatibility.ts, which classifies a database's migration ledger
against the migrations a build carries — current, unmigrated, behind (with the
pending count), unsupported, ahead, or diverged (naming the first mismatch) —
and phrases the operator's next step for each. The readiness probe returns the
verdict as checks.schema whenever a reachable database is not current, so
GET /api/v1/status answers 503 with the action while the ready-state JSON is
unchanged; the task worker refuses to start with the same message.

migrateDatabase refuses ahead and diverged ledgers with that message and now
returns the versions it applied, which the migrate command prints.

Pin the existing lock and atomicity with migration-lock.test.ts (advisory lock
before the ledger read; a failure after three ledger writes ends in ROLLBACK
with no COMMIT) and migration-lock-runtime.test.ts on PostgreSQL (a held lock
blocks a second migrator until released). upgrade-path.test.ts migrates pg-mem
to 0052_routine_occurrences, seeds a user with a real argon2 credential, their
workspace, group and one message, proves the stale readiness and its message,
upgrades through the fixture, then signs in with the old credential, reads the
old group and message, creates a group and submits a task. The Compose job
gains a step that inserts a future ledger row and requires the ahead refusal.

Add docs/upgrade.md — backup first, supported versions, the upgrade steps, the
post-upgrade smoke, readiness states and the recovery path — and link it from
the README and docs/deployment.md. Fixtures accept an existing pool so a test
can upgrade a pre-seeded database.

Stamp DEPLOY-02 complete-with-external-verification with gate DEPLOY-02-E1 on
REL-01, unblock DEPLOY-03, and update the backlog index and frontier.
Migration 0060_job_acquisition_pause adds an instance-wide pause to
instance_settings; TaskWorker.runOnce checks it first, so no task, routine,
extraction, export or purge work is acquired while a backup holds it.

Add apps/api/src/backup: JobAcquisitionPause (pause, resume, running-job
count); a format-1 manifest carrying the application version and build, the
schema version and full migration ledger, per-table row counts, the retention
setting and every object with its SHA-256, plus a strict parser and a
compatibility check that requires the ledger to equal this build's; a tar
archive (manifest.json, checksums.sha256, database.dump, objects/<ws>/<id>)
whose reader verifies every hash and rejects missing, extra or disagreeing
files before returning anything; BackupService, whose backup pauses
acquisition, waits for running runs and exports up to an idle timeout, dumps,
snapshots the object volume, writes the archive and resumes even on failure,
and whose restore checks integrity, compatibility and an empty database and
object volume before the first write, then loads the dump, applies the
retention setting, writes objects without overwriting and re-counts and
re-hashes everything the manifest recorded; and a CLI (backup, restore,
inspect) that drives pg_dump --format=custom --data-only and pg_restore
--data-only --disable-triggers --single-transaction. The master key is never
read and the archive is asserted free of it.

Add the profile-only backup Compose service with owner credentials, the object
volume and ./backups, install postgresql17-client in the API image, and add a
Compose job step that backs up the populated stack, refuses a restore into it,
recreates the stack from empty volumes, restores, requires consistency.ok and
compares row and object counts.

Add docs/backup-restore.md; point docs/upgrade.md, docs/deployment.md and the
README at the tool. Stamp DEPLOY-03 complete-with-external-verification with
gate DEPLOY-03-E1 on REL-01 and update the backlog index and frontier.
Add the web manifest (standalone, /app start URL, SVG plus 192/512 PNG and a
maskable icon) and link it, the theme colour, colour scheme and icons from
app.html. Add an online-first service worker that caches only build assets,
static files and the /offline page, serves shell assets from cache, sends every
navigation and API request to the network, never caches /api, streams, feeds
or downloads, and answers an unreachable navigation or submitted form with the
offline page at status 503 so no write is ever presented as successful.

Add a root layout with a skip link to a focusable content target, an
online/offline status banner, always-visible focus outlines and phone-width
rules (tables scroll inside their box, headers wrap, main padding shrinks), and
a prerendered /offline page with an explicit status.

Pin Last-Event-ID forwarding through the notification stream proxy so a
reconnecting EventSource replays the gap. Add an SSR accessibility audit of the
sign-in, setup, workspace, offline and approval markup (one h1, a main
landmark, labelled controls, named buttons, image alt text) with WCAG contrast
checks for the shared palette, and pwa.spec.ts covering installability, the
offline shell and a refused offline submit, phone and desktop layouts without
horizontal overflow, keyboard skip navigation, axe scans and stream resumption.

Document the PWA in the README. Stamp PWA-01 complete-with-external-verification
with gate PWA-01-E1 on REL-01 and update the backlog index and frontier.
Add docs/release-acceptance.md, which maps each REL-01 acceptance criterion to
the Compose job steps, PostgreSQL jobs, browser scenarios and integration
suites that prove it, describes how to cut a release candidate, and lists the
manual checks a release manager runs on it.

Add infra/release-manifest.mjs, which writes the reproducible release document
(commit, package versions, schema tip, pinned Node and PostgreSQL bases, built
image ids) and a test proving two runs on one tree agree. Link the guide from
the README.

Move REL-01 to ready-for-agent now that every blocker is implemented, record
the evidence map and the remaining external steps on the ticket, and update the
backlog index and frontier.
… full test run found

Running every layer locally — Playwright in Chromium, the PostgreSQL suites,
the S3 suite and the Compose job — surfaced defects the pg-mem and HTTP
walkthroughs could not:

- The runtime role had no privileges on the tables added since ROUT-01
  (notifications, group_notification_settings, conversation_event_mentions,
  instance_settings, workspace_exports, the purge manifest and receipts) and no
  UPDATE on the workspace deletion columns, so the worker's pause check, the
  inbox, mentions, exports and soft deletion all failed under openbot_runtime.
  The privilege script now grants each of them the minimum it needs.
- The append-only and immutability triggers refuse every DELETE, so the
  scheduled purge could not run outside pg-mem. Migration
  0061_purge_role_exemptions re-emits every guard attached to a DELETE trigger,
  generated from the ledger and applied after the late guard blocks, with a
  first clause that lets current_user = 'openbot_purge' through; the audit
  ledger's guard is excluded. The privilege script provisions that LOGIN role
  with SELECT and DELETE on exactly the handler tables (pinned against
  PURGE_HANDLERS) plus the rows the purge writes, the worker connects it via
  OPENBOT_PURGE_DATABASE_PASSWORD and reports task_worker_retention_unconfigured
  without it, and purge-runtime.test.ts proves the owner and runtime role stay
  refused while the purge role removes a deleted workspace and keeps its
  neighbour. Compose, .env.example, CI and the deployment and upgrade guides
  carry the new setting.
- The notifications page listened for unnamed SSE messages while the stream
  emits named events, so live updates never arrived in a browser; it now
  subscribes to each workspace event type.
- The routine form only revealed the cron fields after a server round trip; the
  kind select is now bound so they appear immediately. The routine e2e fixture
  lacked the member, routing and notification services the group page loads.
- The deletion e2e fixture assumed the seven-day grace the lifecycle no longer
  hard-codes; it now sets the instance retention it expects.
- The backup Compose service built the API image a third time in parallel,
  which races on Docker Desktop; it reuses the built image instead.
- pwa.spec.ts waits for the controlling worker, exercises the setup form
  offline on an unclaimed instance and visits real administration routes.
- Ledger-tip assertions in two PostgreSQL suites and the release manifest
  follow MIGRATION_VERSIONS; the legacy 0022 cancellation test adds the
  deleted_at column the current services require ahead of its migration.

Add playwright.local.config.ts for running the browser suite with Playwright's
own Chromium, apps/api/scripts/run-ci-job.mjs for running one Verify job's
steps locally, a postgres-purge CI job for the new PostgreSQL suite, and a
guide section on running every CI layer on a developer machine. Record the run
and its findings on the REL-01 ticket, the DATA-05 ticket and the frontier.
The remote branch carried four commits from a parallel session: a one-time
routine UI with bot denial (ROUT-01), an OPENBOT_TELEMETRY switch with a no-op
recorder, and Compose health contracts (worker healthcheck, web waiting for a
healthy API). This tree already implements ROUT-01 through PWA-01 and has been
verified at every layer — browser, PostgreSQL, S3 and the Compose job — so the
merge keeps that implementation where both sides built the same thing:

- Routine API routes, service, bot-action policy and the web routine pages stay
  as implemented here; the remote's duplicate routes, pages, per-routine detail
  page and their tests are dropped because the cron routines, purge handlers
  and browser scenarios build on the retained versions.
- The worker healthcheck and the web service's service_healthy dependency on
  the API are kept, and both Compose contract tests assert them.
- The telemetry switch is not carried: this tree ships no telemetry code at all
  and idle-outbound.test.ts proves an idle API and worker make no outbound
  request, which is the stronger promise the README documents. The remote's
  deploy-baseline contract test is kept without its telemetry case and with the
  backup tool in the service list; the README gains the exact persistence and
  private-network sentences it checks.
- Ticket, index and frontier files keep this tree's later state.
The root lint runs prettier --check; the files added since ROUT-01 were
written without it. Format them and read the purge table list in the grant
script by its quoted names so the formatter's quoting cannot break the test.
All 21 jobs passed on 378af9c. List the gate parts that run satisfies (the
browser scenarios in CI Chromium, the native PostgreSQL purge, the Compose
startup, upgrade, schema-ahead and backup-and-restore steps, pwa.spec.ts) and
the parts still open: restricted-role PostgreSQL suites for routines,
notifications, exports and deletion, the S3 object purge and scheduled-purge
observation, host-level idle-network observation, and the manual PWA and
release checks.
… Compose evidence

Add PostgreSQL suites that run routines, notifications with mentions, exports,
soft deletion with restore and the scheduled purge under the deployed
openbot_runtime and openbot_purge roles, sharing a fixture that provisions both
roles through the privilege script and seeds a workspace through the production
services. The purge suite now also proves a mid-run store failure resumes, the
full-text index forgets the target, and the neighbour keeps its rows and
objects; an S3 suite proves the object purge against the S3-compatible fixture.

Those runs found three defects on real PostgreSQL and fixed them: finalize
deleted invitations before the memberships that reference them; Bot versions
and Bots reference each other through a deferred constraint, so the Bot handler
now deletes its tables in one transaction (handlers may be atomic); and the
generated DELETE-guard exemptions missed CONSTRAINT ... AFTER triggers such as
the delivery-prefix guard. The export update grant gains sha256.

The Compose job observes that an idle stack opens no outbound connection
(infra/verify-idle-network.mjs over /proc/net/tcp), that the owner's session
survives a restore, and that a soft-deleted workspace is purged by the worker
through the deployed stack with only its receipt left; pwa.spec.ts asserts the
browser's own installability verdict through DevTools. A postgres-features CI
job runs the new suites and the object-storage job runs the S3 purge test.
Record the findings on the DATA-05 ticket.

The isolated-schema PostgreSQL suites now migrate the public schema before
creating their own: the SECURITY DEFINER guard functions compile their %ROWTYPE
declarations against public, so on an empty database those suites only passed
when another suite had happened to migrate public first.
All 22 jobs passed, including postgres-features, the extended postgres-purge
suite, the S3 object purge, the browser installability verdict and the Compose
idle-network and scheduled-purge observations. Mark ROUT-01 through PWA-01
complete, record the run on their tickets and on REL-01, and update the backlog
index and frontier: only REL-01's four manual checks remain.
Setup, invitation acceptance and the API validator now accept passwords of
eight characters or more; the upper bound of 1024 bytes is unchanged. The
setup and join forms, their error messages, the page test and the browser
fixture follow.
@Blackman99
Blackman99 merged commit 996b4a2 into main Sep 7, 2026
22 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant