c24ea815f3
- 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>
198 lines
12 KiB
Markdown
Executable File
198 lines
12 KiB
Markdown
Executable File
---
|
||
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 && <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
|