Files
stargue-publishing-engine/docs/handoffs/2026-05-13-handoff.md
T
Angelo B. J. Luidens c24ea815f3 docs: record Gate 0.8 resolution + Gate 0.9 LinkedIn app / CMA progress
- deferred-gates.md: Gate 0.8 RESOLVED — clean-room Postgres 17 + Redis 7 in a
  dedicated Dokploy project; service names, hosts, env-var locations recorded.
- linkedin-apps.md: app created (Client ID 78s8f53y5spyo4), company-page +
  business-email verified, Community Management API Development Tier submitted
  (review in progress). Notes that other products grey out while CMA is pending.
- docs/handoffs/2026-05-13-handoff.md: committed (was untracked).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 15:31:02 -04:00

198 lines
12 KiB
Markdown
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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.13.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.43.9, 4.14.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 28 (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 && <cmd>'
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