--- created: 2026-05-13T12:35 updated: 2026-05-13T12:35 tags: --- # Publishing Engine — Session Handoff 2026-05-13 **Purpose:** let the next session pick up cold with full state of the Stargue Publishing Engine build. Source: this vault note + mirror at `docs/handoffs/2026-05-13-handoff.md` in the repo. **Repo:** `https://git.stargue.net/admin/stargue-publishing-engine` (local: `/home/devuser/projects/stargue-publishing-engine/` via WSL Debian) **Plan v2 (SSoT):** [[Plan - Phase 1 Automated Publishing Engine|Plan - Phase 1 Automated Publishing Engine.md]] (vault) + `docs/plans/2026-04-19-phase1-plan.md` (repo mirror) **PRD:** [[Publishing Engine PRD]] **Panel verdicts:** `docs/plans/bmad-panel-verdicts-publishing-engine-phase1-20260419.json` (1 APPROVE, 6 REVISE, 0 REJECT — all principles ≥3) **Doctrine:** `.clinerules/12-foundational-principles.md` adopted from DQMS --- ## Where we stand — quick read | Stage | Status | Commit | Tests | Notes | |---|---|---|---|---| | 0 — Governance + scaffolding | ✅ Complete | `1dc1a1a` (2026-04-19) | n/a | 11 workspaces typecheck clean | | 1 — Shared packages | ✅ Complete | `e529651` (2026-04-26) | 50/50 | 4 packages: schema, sanitize, linkedin-client (stubs), observability | | 2 — DB + middleware + API | 🟡 Partial | `c73b7e4` (2026-04-26) | 79/79 cumulative | Live-DB integration tests blocked by **gate 0.8** | | 3 — LinkedIn MCP server | 🟡 Partial | `7aa3137` (2026-04-26) | 106/106 cumulative | Live-LinkedIn blocked by **gate 0.9** | | 4 — Scheduler + cadence | 🟡 Partial | `a4e4306` (2026-04-26) | 117/117 cumulative | BullMQ + integration blocked by **gates 0.8 + 0.9** | | 5 — Admin dashboard (Next.js + A11y) | ❌ Not started | — | — | Independent of gates; can start anytime | | 6 — stargue-com/.net integration | ❌ Not started | — | — | Independent of gates | | 7 — Content + metrics bootstrap | ❌ Not started | — | — | 7.1 + 7.2 independent of gates | **Last activity:** 2026-04-26 (commit `a4e4306`). Repo idle since. Today is 2026-05-13 — ~2.5 weeks of pause. --- ## Deferred gates (the real blockers) ### Gate 0.8 — Postgres + Redis on PaaS **Status:** Dokploy MCP was loaded in the 2026-04-19 session but verification not completed before pause. **Resume action:** with Dokploy MCP attached, run `mcp__dokploy__project-all` → identify Stargue PaaS project → `mcp__dokploy__postgres-search` + `mcp__dokploy__redis-search` to see existing instances; provision new ones in a `stargue-publishing-engine` project if none are shareable. **Why it blocks:** Stage 2 live-DB integration tests, Stage 3 token-store live tests, Stage 4 BullMQ worker, Stage 5 admin auth flow. **Next steps when resolved:** 1. Run `0000_initial_schema.sql` migration on the new DB 2. Run `bun run db:migrate:rehearse` (script lives at `packages/schema/db/migrations/rehearse.ts`) 3. Wire connection strings into Dokploy secrets `DATABASE_URL`, `REDIS_URL` 4. Re-enable the integration tests gated behind `INTEGRATION=1` ### Gate 0.9 — LinkedIn Developer Portal apps **Status:** Pending Angelo's manual action. Checklist ready at `docs/linkedin-apps.md`. **Why it blocks:** Stage 3.1–3.7 live posting, Stage 4 end-to-end publish, Stage 7.4 first scheduled post. **Next steps when resolved:** 1. Angelo registers app at linkedin.com/developers/ linked to Stargue Company Page `urn:li:organization:2605890` 2. Enables Sign-In with OpenID Connect + Share on LinkedIn (instant self-service) 3. Submits Community Management API + MDP partner-access requests (multi-week review — file ASAP to start the clock) 4. Drops `LINKEDIN_CLIENT_ID`, `LINKEDIN_CLIENT_SECRET`, `LINKEDIN_TOKEN_ENCRYPTION_KEY` into Dokploy secrets 5. Update `docs/linkedin-apps.md` registry with app IDs --- ## What's already built (don't re-invent) ### `packages/schema` (full SSoT for data shapes) - `frontmatter.ts` — Zod schema for vault frontmatter (status, language, outlets[], sanitize, scheduled, version) - `db.ts` — Drizzle schema for 7 tables: content, publications (with `idempotency_key UNIQUE`), approvals, metrics, linkedin_tokens, audit, outlet_feature_flags - `rate-limit.ts` — token-bucket implementation, pure-function - `csrf.ts` — double-submit cookie pattern - `authz.ts` — Cerbos-equivalent policy primitives - `api-contracts.ts` — Zod request/response schemas for admin API - `drizzle.config.ts` + `db/migrations/0000_initial_schema.sql` + `0000_initial_schema.down.sql` + `db/migrations/rehearse.ts` ### `packages/sanitize` (fail-closed content pipeline) - `rules.ts` — PRIVATE_PATH_PREFIXES (Family Matters, Financial Matters, Journal, Clients NDA…), PRIVATE_PATH_PATTERNS, PRIVATE_TAGS (`#private`, `#heal-internal`, `#confidential`), OUTLET_LENGTH_LIMITS - `errors.ts` — SanitizeError with codes: PRIVATE_PATH_BLOCKED, PRIVATE_TAG_BLOCKED, WIKILINK_TO_PRIVATE_PATH, OUTLET_LENGTH_EXCEEDED - `index.ts` — `sanitize(markdown, opts)` returns `{ body, contentHash, warnings }` — strips wikilinks/embeds/dataview/callouts, enforces blocklist + tag firewall + length, computes SHA-256 hash - `corpus.test.ts` — Stage 1.2 DoD test: 12 private + 6 clean fixtures round-trip correctly ### `packages/linkedin-client` (Posts API client + token store) - `types.ts` — LinkedIn Posts API request/response types (Posts API v202404, not legacy UGC) - `client.ts` — HTTP client; respects `LinkedIn-Version: 202404` + `X-Restli-Protocol-Version: 2.0.0` - `token-store.ts` — encrypted persistence; **uses AES-256-GCM via `node:crypto`** (NOT libsodium — see D-001 in `docs/deferred-gates.md` for the deviation rationale: libsodium-wrappers ESM bug under Bun) - `client.test.ts` + `token-store.test.ts` — 4/4 round-trip tests pass ### `packages/observability` (Pino + correlation IDs) - `index.ts` — Pino logger with PII-redaction paths (access_token, refresh_token, client_secret, *_ct fields), `newCorrelationId()`, `child(bindings)` helper ### `apps/mcp-linkedin` (Stage 3 partial — structural complete, live blocked) - `server.ts` — `createServer` factory pattern, injectable deps (KillSwitch, AdvisoryLock); MCP_SPEC_VERSION pinned `2024-11-05`; `validateToolCall`, `listTools`, `outletForAuthorUrn` helpers - `tools.ts` — 9 Zod tool schemas: whoami, auth_status, create_post, create_article, upload_media, create_post_with_media, delete_post, get_post_metrics, get_profile_stats - `oauth.ts` — auth-code URL builder + HMAC-protected state parameter (no PKCE — LinkedIn 3LO doesn't support it) - `kill-switch.ts` — `outlet_feature_flags` reader with cache invalidation - `refresh-lock.ts` — Postgres advisory-lock primitive; **4-concurrent rotation test passes** (TEA gap 3 closed) ### `apps/scheduler` (Stage 4 partial — pure pieces complete) - `cadence.ts` — Beta(1,19) posterior optimizer, `propose(history, config) → [Proposal]`; all 4 thresholds verified (≥8 samples, ≥4 weeks, ≥20% ratio, disjoint 95% CIs); never auto-applies (RBR) - `publish-loop.ts` — state-machine transition function `pending→queued→dispatching→published`, retry-vs-DLQ branch, cancellation path - `worker.ts` — entry stub (BullMQ wiring blocked by Redis on gate 0.8) ### `apps/admin` (not started) - `src/index.ts` — stub only. Next.js scaffold + Authentik SSO + queue/publications/auth/metrics pages all pending. --- ## Architecture decisions worth remembering 1. **D-001 token cipher: AES-256-GCM, not libsodium secretbox.** Bun ESM bug in `libsodium-wrappers`. Same AEAD security profile. Documented in `docs/deferred-gates.md`. Remediation path noted if libsodium fixes the bug. 2. **Scheduler imports `linkedin-client` directly** — MCP transport is stdio-only for interactive Claude Code use. No HTTP MCP transport in Phase 1 (architect gap 2). 3. **`apps/api` collapsed into `apps/admin`** — Next.js App Router route handlers cover the API surface; less infra (architect gap 1). 4. **LinkedIn Posts API `/rest/posts`** — NOT the legacy `/v2/ugcPosts`. Headers: `LinkedIn-Version: 202404`, `X-Restli-Protocol-Version: 2.0.0`. 5. **No PKCE in OAuth** — LinkedIn 3LO doesn't document it; confidential-client with HMAC `state` parameter is what we use. 6. **`idempotency_key UNIQUE` on `publications`** — sha256(content_id || outlet || scheduled_at). Prevents double-post on retry. 7. **Kill-switch via `outlet_feature_flags` table** — fast disable without redeploy. Cached at MCP server with invalidation on table change. --- ## Next session — priority order ### Path A: Resolve gates first (recommended if Angelo + Dokploy access available) 1. **Gate 0.8** — Dokploy MCP query → provision Postgres + Redis in `stargue-publishing-engine` project on `sg-paas-s1.stargue.net` 2. **Gate 0.9** — Angelo files LinkedIn Dev Portal apps (~30 min for OIDC + Share; submit Community Management API + MDP requests to start the partner-approval clock) 3. With both gates open, unblock Stages 2.6, 3.4–3.9, 4.1–4.3 live integration tests 4. Move to Stage 5 (admin dashboard) ### Path B: Continue parallel work (if gates remain blocked) 1. **Stage 5** — Admin dashboard scaffold: Next.js App Router + shadcn/ui + Nine Laws design tokens; pages `/queue`, `/publications/:id`, `/auth/linkedin`, `/metrics`; A11y CI (axe-core + @axe-core/playwright). Authentik stub for local dev. 2. **Stage 6** — Add `@stargue/sanitize` + `@stargue/schema` as workspace deps to stargue-com + stargue-net; replace ad-hoc frontmatter parsing; assert zero wikilinks in rendered HTML. 3. **Stage 7.1** — Run `/market audit` baseline on stargue.com + stargue.net; save to `docs/marketing-baseline-2026-05.json`. 4. **Stage 7.2** — Draft 7 Cs posts 2–8 (Commitment, Co-Creation, Connection, Communication, Celebration & Course Correction, Caring & Crossing, Synthesis) — long-form for stargue.com + LinkedIn-optimized variants. Source content from `7 Cs LinkedIn Series - Continuation Plan.md`. Run each through autoresearch optimization to composite ≥8. ### Path C: Mixed (most realistic) - Angelo does Gate 0.9 in parallel (~30 min self-service; partner approvals run async for weeks) - Claude provisions Gate 0.8 via Dokploy MCP - Concurrently start Stage 5 and 7.2 --- ## Open items / things to verify 1. **MDP + Community Management API timeline** — once filed, track approval state weekly. MDP rejection is the trigger for the 60-day-re-auth fallback. 2. **Authentik instance reachability** — plan §8 q3 said "shared instance for all Stargue services"; needs runtime verification before Stage 5.2. 3. **Neon free-tier mirror** — Stage 7.5 DR test pending. Set up nightly `pg_dump → Neon` cron when gate 0.8 resolved. 4. **stargue-com/.net package.json sync** — collision was fixed (commit `fd43214` on stargue-net) — verify both repos still align before Stage 6. 5. **LinkedIn algorithm-reach risk** — plan §9 row added by analyst panel. Once first 4 posts ship, monitor engagement-delta vs Angelo's existing manual baseline; if suppression detected, fall back to draft-and-prompt workflow. --- ## Files to read first in next session In order, for cold start: 1. This handoff 2. `docs/plans/2026-04-19-phase1-plan.md` (in the repo) — full plan v2 3. `docs/deferred-gates.md` — for D-001 + gate status 4. `CLAUDE.md` in the repo — for governance + canonical LinkedIn facts 5. Last 4 commits via `git log` — for exact stage progress 6. `docs/linkedin-apps.md` — for Angelo's manual checklist --- ## Memory notes already saved - `project_publishing_engine.md` — overall project state - D-001 cipher deviation is in `docs/deferred-gates.md` (in repo) — no separate memory note needed since the deviation is project-local - BMAD panel verdicts cached at `docs/plans/bmad-panel-verdicts-publishing-engine-phase1-20260419.json` --- ## Commands cheatsheet ```bash # WSL Debian — always run there wsl -d Debian -- bash -c 'cd /home/devuser/projects/stargue-publishing-engine && ' bun install # workspace deps bun run typecheck # all 11 workspaces bun run test # 117/117 currently passing bun run db:migrate # apply Drizzle migrations (needs DATABASE_URL) bun run db:migrate:rehearse # expand-then-contract rehearsal # Gitea git push # auto-deploys via Dokploy webhook (Phase 0 sites); # Publishing Engine apps deploy on separate Dokploy stack (Stage 5+) ``` --- ## Related - [[Publishing Engine PRD]] - [[Plan - Phase 1 Automated Publishing Engine]] - [[7 Cs LinkedIn Series - Continuation Plan]] - [[Design Philosophy - The Nine Laws of the Hearth]] - [[Outlet Profiles/LinkedIn]] - [[Implementation Progress]] — update with this handoff's status table after the next session