# Deferred gates Items that could not be completed in the Stage 0 execution window and must be resolved before the gate they block. ## Gate 0.8 — Postgres + Redis on PaaS **Status:** ✅ RESOLVED 2026-06-03 (via Dokploy MCP, resolution option 3 — clean-room stack). **Reason (historical):** Dokploy MCP tools (`mcp__dokploy__*`) were not loaded in the Stage 0 session, and `DOKPLOY_API_KEY` was not in the WSL environment. Direct API query not possible in-session. **Blocks (now cleared):** Stage 2 (database + API) — the scheduler and admin cannot run without a Postgres + Redis pair. ### Resolution (2026-06-03) Provisioned a dedicated clean-room Dokploy project on `sg-paas-s1.stargue.net`, isolating blast radius from other PaaS apps (option 3). | Item | Value | |---|---| | Dokploy project | `stargue-publishing-engine` (`projectId: Y6R_4ML2N0Vsztk0UVi1W`) | | Environment | `production` (`environmentId: hkvob7vx02lzkn3GhpHMD`) | | Postgres service | "Publishing Engine Database" (`postgresId: O_BjqkxljdMU3xLb3q09M`) — image `postgres:17`, status `done` | | Postgres internal host | `stargue-pubeng-postgres-n9c1gc` (Docker network alias) port `5432` | | Postgres db / user | `publishing_engine` / `publishing_engine` | | Postgres volume | `stargue-pubeng-postgres-n9c1gc-data` → `/var/lib/postgresql/data` | | Redis service | "Publishing Engine Redis" (`redisId: fZR9Bdx8-3d99uOrrqFe5`) — image `redis:7`, status `done` | | Redis internal host | `stargue-pubeng-redis-9tebfd` (Docker network alias) port `6379` | | Redis volume | `stargue-pubeng-redis-9tebfd-data` → `/data` | **Connection-string env-var locations (NOT stored in git):** - `DATABASE_URL` = `postgres://publishing_engine:@stargue-pubeng-postgres-n9c1gc:5432/publishing_engine` - `REDIS_URL` = `redis://default:@stargue-pubeng-redis-9tebfd:6379` - Secret **values** live in Dokploy (retrieve via `mcp__dokploy__postgres-one` / `mcp__dokploy__redis-one`). They will be injected as Dokploy secrets on the **app** services (`apps/mcp-linkedin`, `apps/scheduler`, `apps/admin`) once those are created in Stage 5+. No app service exists yet, so there is nowhere to attach them today. **Remaining to fully unblock live DB tests:** 1. Run `0000_initial_schema.sql` against the new DB. The internal hostname is only resolvable inside the Dokploy Docker network, so either (a) temporarily add an external port via `mcp__dokploy__postgres-saveExternalPort` and run `bun run db:migrate` from WSL with the external `DATABASE_URL`, then remove the port; or (b) run the migration from a one-shot container inside the stack. 2. Run `bun run db:migrate:rehearse` (forward-then-rollback row-count hash check). 3. Re-enable `INTEGRATION=1`-gated tests with the connection strings wired. **Acceptance (met):** service names, hostnames, and connection-string env-var locations recorded above; secret values held in Dokploy, not committed. ## Gate 0.9 — LinkedIn Developer Portal apps **Status:** Pending Angelo's manual action. **Reason:** Dev Portal registration cannot be automated — requires human auth at `linkedin.com/developers/`. **Blocks:** Stage 3.1 (OAuth flow) live integration — structure + contract tests can proceed in parallel. **Resolution:** follow the checklist in [`linkedin-apps.md`](./linkedin-apps.md). Expected time ~30 min for OIDC + Share; multi-week for Community Management API + MDP partner approvals. **Acceptance when resolved:** `linkedin-apps.md` registry filled in with app IDs; client credentials stored in Dokploy secrets `LINKEDIN_CLIENT_ID`, `LINKEDIN_CLIENT_SECRET`, `LINKEDIN_TOKEN_ENCRYPTION_KEY`. ## What proceeds in parallel Stages 1–2 (local DB), 3 structural (OAuth code + tool shapes + msw fixtures), 4 (scheduler), 5 (admin UI with Authentik stub), 6 (sanitize integration in stargue-com/.net), 7.1 (`/market audit` baseline), 7.2 (7 Cs post drafting) can all proceed without these gates. Live integration tests and first production publish require both gates resolved. --- ## D-001 — Token store cipher: AES-256-GCM instead of XSalsa20-Poly1305 **Plan §3.2 specified:** libsodium `crypto_secretbox` (XSalsa20-Poly1305), per architect gap 3. **Implemented (2026-04-26):** Node stdlib `aes-256-gcm` via `node:crypto`. Same AEAD security profile (256-bit key, 96-bit nonce, 128-bit auth tag), zero external dependencies. **Reason:** `libsodium-wrappers` and `libsodium-wrappers-sumo` (v0.7.16) both have a known ESM resolution bug under Bun where the loader fails to find `./libsodium.mjs` / `./libsodium-sumo.mjs` from within their own dist folder. Reproduced cleanly on Bun 1.3.11. AES-256-GCM is the recommended AEAD when libsodium isn't available — same security guarantees, FIPS-compliant, in stdlib of every JavaScript runtime (Node, Bun, Deno, Cloudflare Workers). **Security review:** - AEAD: ✅ (authenticity + confidentiality) - Key length: 256 bits (same as XSalsa20-Poly1305) - Nonce length: 96 bits (sufficient for random nonces; collision probability < 2^-32 after 2^32 messages — well within Phase 1 envelope of <100k tokens lifetime) - Tag length: 128 bits (same as XSalsa20-Poly1305) - Implementation: Node stdlib (audited, FIPS 140-2 validated under common builds) **Remediation path:** When `libsodium-wrappers` ships a Bun-compatible ESM build, migrate by: (1) restore dep, (2) re-implement encrypt/decrypt with `crypto_secretbox_easy`, (3) one-time decrypt-and-re-encrypt migration on `linkedin_tokens.*_ct` rows, (4) drop AES-GCM code path. **Risk:** Low. Both ciphers offer equivalent security for this use case (encrypting OAuth tokens at rest in Postgres). The deviation is purely about the cipher primitive, not the threat model or key management. **Status:** Accepted. 4/4 token-store round-trip tests pass. Production-ready.