Status: Draft for team sign-off
Scope: Production launch (v1 / GA) of the ObjectStack framework monorepo
Last reviewed: 2026-06-04 (main @ 9f311f8 — ADR-0030 P3b-2 digest)
Each item has an owner, a verification box, and a sign-off box. The two are deliberately separate:
- ☐ Verify — a named engineer has confirmed the finding is real (or a false positive) by reading the actual code / running a repro. Several items below originate from an automated package-by-package sweep and are not yet hand-verified — do not action them before verifying.
- ☐ Sign-off — the fix is merged (or the risk is formally accepted and the acceptance recorded in the "Notes" column).
A "false positive" still gets ticked on Verify, with a one-line note explaining why no fix is needed. Do not mark a P0 signed-off on the strength of the sweep alone.
Priority key: P0 = blocks launch · P1 = fix at/just-after launch · P2 = test-coverage / hardening gap · Roadmap = explicitly out of v1.
The codebase is mature and disciplined. A repo-wide scan found zero
TODO/FIXME/HACK markers, console.log in source, empty catch blocks,
@ts-ignore, or hard-coded secrets. main CI is green (Build/Test Core, Lint &
Type Check, CodeQL). The core tier — spec, objectql, plugin-security,
runtime, the SQL drivers, service-ai — is assessed production-ready.
Launch risk is concentrated in a small number of themes below, not in code quality. The single most important caveat: the security- and data-integrity findings (§P0) are from an automated sweep and MUST be hand-verified before any fix or acceptance.
Every P0 below must be hand-verified first — some may be false positives or already-guarded. Do not ship without each row at Verify ✓ and Sign-off ✓.
- Area:
plugin-auth—src/auth-manager.ts(~L1052–1071,generateSecret()) - Risk: If
OS_AUTH_SECRETis unset in production, the manager logs a warning and falls back todev-secret-<timestamp>. Session tokens become predictable → session forgery. (HIGH, security) - Action: Throw (fail boot) when no secret is configured and
NODE_ENV === 'production'; add a pre-boot config validation. DocumentOS_AUTH_SECRETas a required go-live env var. - Owner: _______ · Verify ✅ (confirmed real @
main) · Sign-off ☐ · Notes: Fixed —generateSecret()throws in production; +3 tests. Awaiting human sign-off.
- Area:
plugin-security—src/security-plugin.ts:309–312 - Risk: A metadata-resolution error is swallowed and the request proceeds via
next()with no permission checks. If the metadata service degrades, every user bypasses RBAC/RLS. (HIGH, security) - Action: Add a circuit-breaker + ERROR-level alerting; add an integration test asserting "metadata service down ⇒ request denied", not allowed. Decide fail-closed vs. fail-open explicitly and record the decision.
- Owner: _______ · Verify ✅ (confirmed real @
main) · Sign-off ☐ · Notes: Decision = fail-closed.catchnow logs ERROR + throwsPermissionDeniedError; system ops still bypass. +2 tests. Awaiting human sign-off.
- Area:
driver-sql—src/sql-driver.ts:1565, 1656 - Risk: The
contains/$containsoperator embeds the user value into a%...%LIKE pattern without escaping%/_. Parameterization prevents SQL injection, but a%value matches everything → logic-level filter bypass. (HIGH, data) - Action: Escape
%and_(and the escape char) before building the LIKE pattern; add a test with a%/_payload. - Owner: _______ · Verify ✅ (confirmed real @
main) · Sign-off ☐ · Notes: Fixed — escape%/_/\+ explicitESCAPE '\'(SQLite needs it); +3 tests. Awaiting human sign-off.
- Area:
driver-mongodb—src/mongodb-filter.ts:82–84 - Risk: Operator keys are passed straight to MongoDB with no allowlist, so
$where/$function(server-side JS) reach the engine → query-intent bypass and a potential JS-execution surface. (HIGH if MongoDB is a launch driver) - Action: Allowlist safe operators (
$eq/$ne/$gt/$gte/$lt/$lte/$in/$nin/$and/$or/...); reject unknown ones at filter-build time. If MongoDB is not a v1 driver, mark Roadmap instead. - Owner: _______ · Verify ✅ (confirmed real @
main) · Sign-off ☐ · Notes: Fixed — translator now rejects unknown$-operators (blocks$where/$function); +4 tests. Awaiting human sign-off. (Still confirm whether MongoDB is a v1 driver.)
- Area:
service-realtime(in-memory-realtime-adapter.ts),service-feed(in-memory-feed-adapter.ts) - Risk: Publish/subscribe and feed storage are process-local. In a multi-node deployment, clients connected to node B never receive node A's events, and feed data is lost on restart / unbounded in memory. (HIGH for HA/cluster; N/A for single-instance)
- Action (cluster launch): Provide a Redis-backed realtime adapter and a
DB-backed feed adapter, or formally restrict v1 to single-instance and
document it as non-HA. Enforce a
maxItemscap if shipping in-memory feed. - Owner: _______ · Verify ☐ · Sign-off ☐ · Notes: _______
- Area:
embedder-openai(src/index.ts:~157),connector-rest(~L157),connector-slack(~L155),connector-mcp - Risk: Naked
fetchwith no timeout or 429/5xx backoff → a slow or rate-limited external API hangs the entire agent turn with no recovery. - Action: Add a default request timeout (e.g. 30s, configurable) + exponential backoff (3 tries) + a circuit breaker; tests for 429 / timeout paths.
- Fix: New shared
resilientFetch(@objectstack/spec/shared) — 30s per-attempt timeout (AbortController) + exponential backoff with jitter (3 tries) on network errors / 429 / 5xx, honouringRetry-After; never retries a caller-initiated abort. Wired intoconnector-rest,connector-slack,embedder-openai.connector-mcpuses the MCP SDK transport, so it gets a 30s per-requesttimeoutoncallTool/listToolsinstead. +13 tests (helper 9, connector retry 1, plus existing suites green). - Deferred (follow-up, not blocking): a circuit breaker — it's stateful and per-host; timeout + backoff already removes the "hangs the agent turn / no recovery" risk. Making timeout/retry per-call configurable (currently sensible defaults) is a small follow-up.
- Owner: _______ · Verify ✅ (confirmed real @
main) · Sign-off ☐ · Notes: Timeout + backoff shipped across all 4 paths; circuit breaker deferred (rationale above). Awaiting human sign-off.
- Area:
service-automation(in-memory exec logs, hard 1000 cap),service-job(sys_job_run, no retention),service-messaging(event log retention is opt-in) - Risk: Long-running pods OOM; history tables grow without bound.
- Action: Make retention default-on for all event/run tables; schedule sweepers at startup; persist automation logs to a table rather than memory.
- Owner: _______ · Verify ☐ · Sign-off ☐ · Notes: _______
- Area:
core(kernel.ts),cli(serve.ts),plugin-hono-server(adapter.ts) - Verification finding: The sweep's "no SIGTERM/SIGINT handling" is wrong.
Kernel.registerShutdownSignals()(called at start) already handles SIGINT/SIGTERM/SIGQUIT →shutdown()→performShutdown()(ordered plugin destroy in reverse +kernel:shutdownhook +onShutdownhandlers), bounded by a default 60sshutdownTimeout.serve.tsboots through the kernel, so the production path inherits all of this. The ≥60s grace floor already exists. - Real (narrower) gap — FIXED: the standalone Hono server's
close()calledcloseAllConnections(), which force-killed in-flight requests instead of draining them. Replaced with:server.close()(stop new + drain active) +closeIdleConnections()(release idle keep-alive), and force-close only after a bounded drain window (default 10s, < the kernel's 60s). +2 integration tests. - Residual (not blocking): embedding framework adapters (express/fastify/…)
intentionally leave signal handling to the host app; cluster/Redis close should
be registered via
kernel.onShutdown(...)by the cluster plugin — confirm it is. - Owner: _______ · Verify ✅ (mostly false positive; drain bug fixed) · Sign-off ☐ · Notes: Kernel shutdown already correct; hono drain fixed + tested. Awaiting human sign-off.
- Area:
rest—src/rest-server.ts:~504–530 - Risk:
resolveByHostname()runs on every unscoped request → control-plane latency spike under load; silent fallback to default project masks it. - Action: Add an in-memory TTL cache (~30s) for
hostname → environmentId. - Owner: _______ · Verify ✅ (confirmed real @
main) · Sign-off ☐ · Notes: Fixed —RestServer.resolveHostnameCached()caches hostname→env (positive and negative) for 30s across all 3 call sites; +3 tests. Awaiting human sign-off.
- Area:
service-cluster-redis—src/pubsub.ts:~75–90 - Risk:
publish()doesn't wait for subscribers; a crash right after a schema change leaves other nodes with a stale schema until the next full reload. - Action: Acceptable for non-critical events if documented; ensure schema
mutations re-sync on error boundaries (history exists in
sys_metadata_history). Record the durability contract. - Durability contract (recorded): Redis pub/sub is at-most-once by design
(already noted in
pubsub.ts).metadata.changedis a cache-invalidation hint only — the durable source of truth is the transactional write tosys_metadata(+sys_metadata_history). A node that misses the event serves its cached schema until the next reload and loses no data (self-heals on reload/restart against the DB). Documented inpubsub.ts publish(). Accept for v1: no exactly-once state may flow through this channel — durable state uses an outbox. - Owner: _______ · Verify ✅ (by design — contract recorded) · Sign-off ☐ · Notes: No code fix needed; risk accepted with rationale + code comment. Awaiting human sign-off.
| ID | Area | Gap | Action | Owner | Verify | Sign-off |
|---|---|---|---|---|---|---|
| P2-1 | metadata-fs |
NEEDS-WORK. Atomicity / watch replay consistency under concurrent FS writes; 200ms chokidar self-write suppression is racy on slow/network FS |
Add fault-injection tests (crash mid-write, concurrent put+delete, watch replay); validate writeJsonAtomic |
____ | ☐ | ☐ |
| P2-2 | cli |
Missing serve integration test (kernel init + HMR reload + SIGINT); possible missing environments.ts implementation (test exists) |
Add serve/dev integration test; confirm environments command exists |
____ | ☐ | ☐ |
| P2-3 | plugin-webhooks |
Thin tests (1 test file); eventId dedup can collide on identical-ms timestamps |
Add UUID suffix to eventId; expand auto-enqueuer tests |
____ | ☐ | ☐ |
| P2-4 | service-queue |
CAS claim (read-then-update) is not atomic → tolerated duplicate delivery; empty idempotency key bypasses dedup | Enforce non-empty idempotency key; add a lease safety margin; document at-least-once | ____ | ☐ | ☐ |
| P2-5 | plugin-audit |
sys_session mutations are not audited |
Confirm login/logout are logged elsewhere (sys_audit_log); close the compliance gap if not |
____ | ☐ | ☐ |
| P2-6 | core / runtime |
getService() is sync but may return a Promise → unhandled-rejection footgun for plugin authors |
Tighten the type / add a guard; document the sync-only contract | ____ | ☐ | ☐ |
These are designed but unbuilt and should be named "not in v1" in the release notes, not treated as blockers.
- ☐ Unverified features — confirm stub vs. minimal, then include or exclude:
knowledge-ragflow,connector-openapi(the sweep could not locate full implementations; the packages exist — verify scope before GA). - ☐ Proposed ADRs (roadmap): ADR-0021 (analytics semantic layer), ADR-0022/0023/0024 (connectors / OpenAPI→connector / MCP connectors), ADR-0025/0026 (plugin & client-UI distribution), ADR-0027 (metadata authoring lifecycle), ADR-0028 (naming/namespace isolation), ADR-0029 (kernel object ownership — partially landed: K0 + D7 + several K2 domains), ADR-0033 Phase D (enterprise AI-authoring governance).
- ☐
mainCI green at the release commit (Build/Test Core, Lint & Type Check, CodeQL). - ☐ Pending changesets reviewed (4 at last check) and version bump intentional.
- ☐
pnpm run releasepath verified (build→build-console.sh→changeset publish). - ☐ Required env vars documented for go-live (at minimum
OS_AUTH_SECRET; see P0-1). - ☐ Deployment topology decided: single-instance vs. HA/cluster (drives P0-5).
The per-package findings were produced by an automated, read-only package-by-package
sweep on 2026-06-04 and synthesized here. They are a starting point for review,
not a verified defect list — each item carries its own Verify box for exactly
this reason. File:line references are approximate and must be confirmed against
main before acting.