Phase 1 foundation for the Stargue Publishing Engine (plan v2, BMAD
panel-reviewed 2026-04-19 — 1 APPROVE, 6 REVISE, 0 REJECT; all principles >=3).
- Governance doctrine adopted from DQMS
(.clinerules/12-foundational-principles.md,
.claude/hooks/gate-plan-exit.sh, .claude/skills/bmad-plan/SKILL.md)
- Bun workspaces + Turbo; apps/{mcp-linkedin,scheduler,admin};
packages/{schema,sanitize,linkedin-client,observability}
- Drizzle schema (content, publications, approvals, metrics,
linkedin_tokens, audit, outlet_feature_flags) with idempotency_key
UNIQUE and kill-switch table per TEA/dev panel revisions
- LinkedIn API canon: Posts API /rest/posts (not legacy UGC); OAuth
auth-code without PKCE; secretbox (not sealed-box); Community
Management API as separate approval gate from MDP
- Frontmatter Zod schema (status, language, outlets[], sanitize,
scheduled, version)
- Pino observability with PII redaction
- Expand-then-contract migration runbook
- Plan + panel verdicts mirrored to docs/plans/
- Deferred gates logged (Dokploy PaaS verification, LinkedIn Dev
Portal app registration)
bun install + bun run typecheck both exit 0 across 11 workspaces.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
194 lines
20 KiB
JSON
194 lines
20 KiB
JSON
{
|
||
"plan_file": "c:\\Users\\aluid\\OneDrive\\Documents\\Obsidian\\Angelo'sSBO\\Stargue\\Projects\\Publishing Engine\\Plan - Phase 1 Automated Publishing Engine.md",
|
||
"created_at": "2026-04-19T10:20:00-04:00",
|
||
"applicable_tier2": ["RBR", "A11y", "Testability"],
|
||
"verdicts": [
|
||
{
|
||
"agent": "analyst",
|
||
"verdict": "APPROVE",
|
||
"confidence": "medium",
|
||
"summary": "Plan is evidence-grounded and principle-aligned; approve subject to resolving Company-Page existence, timeline capacity, Bayesian spec, and LinkedIn algorithmic-reach competitive risk before Stage 3.",
|
||
"gaps": [
|
||
"§4 timeline: 7-day schedule is aggressive given PRD §2 production-problem framing; no FTE/capacity assumption stated",
|
||
"§2/§8: Stargue Company Page URL is both an Objective and an open gate — stakeholder alignment gap",
|
||
"§3.7 cadence optimizer: Bayesian posterior under-specified (prior, thresholds, sample-size beyond ≥8)",
|
||
"§10: multilingual (PAP/NL/ES) deferred, but PRD §5.3/NF-008 and 7 Cs plan assume PAP audience",
|
||
"§9: no competitive risk entry for LinkedIn algorithm/reach-suppression of automated-posted content"
|
||
],
|
||
"principles": {
|
||
"SEBP": {"score": 4, "rationale": "§3.1 monorepo with packages/{sanitize,schema,linkedin-client,observability} gives clear single-responsibility boundaries; scheduler + MCP both consume linkedin-client (no duplication)."},
|
||
"SSoT": {"score": 5, "rationale": "§3.4 sanitize config + §3.5 Drizzle schema + §5 design tokens generated from Nine Laws doc — each concept has one authoritative location."},
|
||
"FPT": {"score": 4, "rationale": "§1 cites retrieved LinkedIn MDP doc with date; §3 stack choices match verified stargue-com/stargue-net inventory. Minor: Bayesian optimizer under-specified."},
|
||
"DiDSP": {"score": 5, "rationale": "§3.3 libsodium sealed-box tokens, Authentik SSO, bearer tokens scheduler↔MCP, rate limits, no public exposure, Zod at every boundary."},
|
||
"PbD": {"score": 4, "rationale": "§5 notes token minimization, 13-month metrics retention, erasure path via token revoke + git delete. Gap: no DPIA note on LinkedIn-side audience data."},
|
||
"OF": {"score": 5, "rationale": "§3.3 correlation IDs, Pino→OpenObserve, PII-redacted content (length+hash only), append-only audit table, proactive T-5d auth-expiry notification."},
|
||
"RBR": {"score": 4, "rationale": "§3.3 fail-safe halt, §3.7 human-in-the-loop for cadence, §3.5 expand-then-contract, §4.3 DLQ. Gap: no rollback drill for published-to-LinkedIn content past 5-min edit window."},
|
||
"A11y": {"score": 4, "rationale": "§3.6 WCAG 2.2 AA, axe-core in CI, keyboard parity, prefers-reduced-motion, contrast on both themes; OAuth screen delegated to LinkedIn. Gap: no screen-reader manual test named."},
|
||
"Testability": {"score": 4, "rationale": "§3 pure core in packages; §7 contract tests against recorded LinkedIn exchanges; injectable clock/ID implied. Gap: cadence-optimizer seams not explicit."}
|
||
}
|
||
},
|
||
{
|
||
"agent": "architect",
|
||
"verdict": "REVISE",
|
||
"confidence": "high",
|
||
"summary": "Architecturally sound and stack-verified, but over-partitioned for a 7-day build — collapse api-into-admin, fix the libsodium primitive, and cite the rate-limit numbers before Stage 0.",
|
||
"gaps": [
|
||
"§3.1 Repo topology adds Turbo + 4 apps + 4 packages for a 7-day plan — architecturally heavy for one-engineer rollout; suggest collapsing apps/api into apps/admin route handlers",
|
||
"§3.3 MCP transport: Streamable HTTP spec version unnamed; scheduler-to-MCP over HTTP within same Docker network adds a hop vs direct library import — justify or use library",
|
||
"§3.3 Token store: libsodium sealed-box is public-key encryption; for symmetric at-rest DB encryption use secretbox or KMS envelope",
|
||
"§3.3 Rate limit numbers (~100/day per member) are uncited",
|
||
"§3.8 Postgres: says 'shared with other PaaS apps (existing cluster)' — verify; §8.5 open question contradicts",
|
||
"§4 Stages: 7-day timeline for OAuth×2 identities + 8 MCP tools + scheduler + admin + A11y CI + E2E is optimistic",
|
||
"§3.4 Sanitization: no test corpus named in Stage DoD (only in §9 risks)",
|
||
"§3.6 Admin: no CSRF strategy named for approval POSTs"
|
||
],
|
||
"principles": {
|
||
"SEBP": {"score": 4, "rationale": "Clean package boundaries (§3.1); mcp-linkedin consumes linkedin-client — but api+admin separation may violate YAGNI for Phase 1."},
|
||
"SSoT": {"score": 5, "rationale": "Frontmatter schema, blocklist, design tokens each single-homed (§3.4, §3.5, §3.6)."},
|
||
"FPT": {"score": 4, "rationale": "Stack verified against actual repos; LinkedIn docs cited with retrieval date. Loses a point on uncited rate-limit figures and MCP transport choice."},
|
||
"DiDSP": {"score": 4, "rationale": "Zod+Authentik+rate-limit+encrypted tokens+audit (§3.3) — solid. Loses point for libsodium sealed-box mis-selection and missing CSRF."},
|
||
"PbD": {"score": 4, "rationale": "Low PII surface, 13-month retention, erasure path named (§5). Token retention policy could be more explicit."},
|
||
"OF": {"score": 5, "rationale": "Correlation IDs end-to-end, OpenObserve integration, body-hash redaction, append-only audit (§3.3, §5)."},
|
||
"RBR": {"score": 4, "rationale": "Expand-only migrations, DLQ, human-gated cadence, fail-safe on auth. LinkedIn delete only within 5-min edit window — no compensating audit-before-publish preview enforcement."},
|
||
"A11y": {"score": 4, "rationale": "WCAG 2.2 AA target, axe-core in CI, keyboard parity, both themes contrast-checked. Screen-reader manual test unlisted in DoD."},
|
||
"Testability": {"score": 4, "rationale": "Pure core in packages, injectable clock, client behind interface, contract tests against recorded exchanges. No named fake for token-store rotation race."}
|
||
}
|
||
},
|
||
{
|
||
"agent": "pm",
|
||
"verdict": "REVISE",
|
||
"confidence": "high",
|
||
"summary": "Strong governance and architecture, but the plan inverts the PRD's revenue-first pivot and fails to reconcile scope with the 4h/week capacity cap — revise scope and timeline before exit.",
|
||
"gaps": [
|
||
"§4 Stages vs PRD §6.5: Plan compresses M1.1–M1.4 (PRD 7 weeks @ 4h/wk = 28h) into ~7 days with no hours-per-stage stated; PRD §9 risk #1 is solo-founder capacity",
|
||
"§2 Objectives: zero mention of revenue conversion (PRD §6.4, F-003); PRD v2 pivot explicitly revenue-first, plan adds infrastructure only",
|
||
"§10: multilingual (PAP/NL/ES) deferred to Phase 2 but PRD F-021 places at P1 and 7 Cs plan publishes EN only",
|
||
"§4 Stage 7: 14 drafts committed in 2 days assumes autoresearch loop is near-free — no hours estimate",
|
||
"§3.6/§3.7 Admin + optimizer are M1.2/M2 scope in PRD; pulling into Phase 1 redefines Phase 1 boundary",
|
||
"§11: MDP application timeline not flagged for revenue-dependent features"
|
||
],
|
||
"principles": {
|
||
"SEBP": {"score": 4, "rationale": "Monorepo boundaries clean (§3.1); standard packages/apps split; no leaky abstractions."},
|
||
"SSoT": {"score": 5, "rationale": "Frontmatter schema, sanitize config, design tokens all single-location; eliminates existing ad-hoc parse."},
|
||
"FPT": {"score": 4, "rationale": "Cites LinkedIn MDP docs with retrieval date; verified repo state; real outlet profile. Does not probe 4h/wk capacity math."},
|
||
"DiDSP": {"score": 5, "rationale": "Zod at boundaries, Authentik SSO, libsodium, rate limiting, audit table, no public MCP exposure."},
|
||
"PbD": {"score": 4, "rationale": "Token retention + encryption + revocation path; 13-month analytics cap; content is public-by-design."},
|
||
"OF": {"score": 5, "rationale": "Correlation IDs through API→scheduler→MCP; OpenObserve dashboards; audit append-only; PII redaction."},
|
||
"RBR": {"score": 4, "rationale": "Human approval on every publish, DLQ, expand-only migrations, feature flag on adaptive optimizer, Telegram fail-safe. No staged rollout for MCP server itself."},
|
||
"A11y": {"score": 4, "rationale": "WCAG 2.2 AA target, axe-core CI, keyboard parity, prefers-reduced-motion, dual-theme contrast. Not yet tested with SR."},
|
||
"Testability": {"score": 4, "rationale": "Pure core in packages, injectable clock/ID, LinkedIn client behind interface with contract tests. Sandbox-or-contract fallback right."}
|
||
}
|
||
},
|
||
{
|
||
"agent": "sm",
|
||
"verdict": "REVISE",
|
||
"confidence": "high",
|
||
"summary": "Architecture and DoDs are largely sprint-ready, but stages must be re-timelined against the PRD's 4h/week cap and 6 DoD items need 'works'-language tightened before dev handoff.",
|
||
"gaps": [
|
||
"§4 DoD uses calendar 'days 1–7' but PRD caps at 4h/week; re-express as Week-1…Week-N at 4h/week or justify",
|
||
"§4 Stage 1.2 DoD 'Sanitizes sample vault note correctly' needs replacement with 'round-trips 12-file private-corpus fixture with 0 leaks and 12 build errors'",
|
||
"§4 Stage 3.4: no cleanup step; add 'test post deleted within 5-min edit window; deletion audited'",
|
||
"§4 Stage 4.2 'End-to-end happy path': specify trace (POST /content → approval → scheduled_at → git SHA + LinkedIn URN recorded, status=published, within 120s)",
|
||
"§4 Stage 5.3 'No violations above minor' needs axe-core severity gate (zero serious+critical) + WCAG contrast ratios",
|
||
"§4 Stage 7.4 'Observed live' not CI-demonstrable; add 'publication row external_url returns HTTP 200 from unauthenticated curl; metrics row at T+48h±1h'",
|
||
"§8 Auth gates block Stage 0 exit but Stage 0 has no 'gates answered' DoD row",
|
||
"§4 Stage 2.1 'expand-only migrations documented': needs pointer to doc location",
|
||
"No sprint sequencing artifact / story backlog — charter requires it for dev handoff"
|
||
],
|
||
"principles": {
|
||
"SEBP": {"score": 4, "rationale": "Clean monorepo boundaries (§3.1); shared packages reused — no duplicated logic named."},
|
||
"SSoT": {"score": 5, "rationale": "§3.4 + §3.5 put frontmatter, sanitize config, tokens in single packages; §5 SSoT row cites file paths."},
|
||
"FPT": {"score": 4, "rationale": "§1 cites LinkedIn MDP doc with retrieval date; verified repo state; rejected n8n with tradeoff. Miss on Phase 0 capacity constraint."},
|
||
"DiDSP": {"score": 5, "rationale": "§3.3 token encryption + §3.5 audit table + §3.6 Authentik SSO + §9 rate limits — layered, concrete."},
|
||
"PbD": {"score": 4, "rationale": "§5 PbD row names retention (13mo), erasure path (token revoke + hard delete), purpose limitation. Missing DPIA note."},
|
||
"OF": {"score": 5, "rationale": "§3.3 per-tool correlation ID + OpenObserve + redaction rule — structured and specific."},
|
||
"RBR": {"score": 3, "rationale": "§5 names approval-gate + DLQ + expand-contract + flagged adaptive cadence. Missing rollback runbook for a bad LinkedIn post past 5-min edit window."},
|
||
"A11y": {"score": 4, "rationale": "§3.6 + Stage 5.3/5.4 name axe-core, keyboard parity, reduced-motion, dual-theme contrast. 'No violations above minor' ambiguous."},
|
||
"Testability": {"score": 4, "rationale": "§7 splits unit/integration/E2E; §3.3 client behind interface; injectable clock mentioned. Cadence-optimizer Bayesian logic has no named test seams."}
|
||
}
|
||
},
|
||
{
|
||
"agent": "ux-designer",
|
||
"verdict": "REVISE",
|
||
"confidence": "high",
|
||
"summary": "Strong security/SSoT/observability spine with Nine-Laws-aware design intent, but A11y is declared-not-designed — add operator flows, SR protocol, dual-theme contrast verification, and reconcile Law-3 temperature with WCAG 1.4.1 before exit.",
|
||
"gaps": [
|
||
"§3.6/§5: A11y names mechanisms but omits user flows; no wireframes/task analysis/cognitive-load budget for /queue approval task (where slip leaks private content)",
|
||
"§3.6: aria-live polite/assertive level unspecified; no error-association spec for OAuth re-auth or approval form (WCAG 3.3.1, 3.3.3, 4.1.3)",
|
||
"§3.6: no screen-reader testing in CI or manual protocol (charter requires SR-tested for score 5)",
|
||
"§3.6: dual-theme contrast promised but neither theme's token values (warm #fefbf6 / cool #f8fafc + Forest/Lime accents) verified against 4.5:1 / 3:1",
|
||
"Nine Laws fit: admin dashboard is operator-tool — no temperature declared; risk of Law-4 amber leakage into status badges",
|
||
"§3.7 cadence optimizer heatmap is color-encoded — Law 3 temperature semantics conflict with WCAG 1.4.1 (color not sole means)",
|
||
"§4 Stage 5: no criterion for focus-trap on modal dialogs, skip-links, or reduced-motion honoring in section fade-in (P8.4)",
|
||
"§11: open questions omit 'primary operator persona and accessibility context'"
|
||
],
|
||
"principles": {
|
||
"SEBP": {"score": 4, "rationale": "Monorepo boundaries in §3.1 are clean; shared packages reused by scheduler + mcp; tradeoff named."},
|
||
"SSoT": {"score": 5, "rationale": "§3.1 shared packages, §3.4 Zod config, §3.6 design tokens inherited from Nine Laws doc — no duplication path."},
|
||
"FPT": {"score": 4, "rationale": "§1 cites LinkedIn docs with retrieval date and verifies repo state; rejects n8n on SSoT grounds."},
|
||
"DiDSP": {"score": 5, "rationale": "§3.3 sealed-box, §3.4 fail-closed sanitizer, §3.5 append-only audit, Authentik + bearer layered."},
|
||
"PbD": {"score": 4, "rationale": "§3.3 token minimization, 13-month analytics retention, erasure path named."},
|
||
"OF": {"score": 5, "rationale": "§3.3/§3.5 correlation IDs, structured Pino logs, OpenObserve dashboards, append-only audit."},
|
||
"RBR": {"score": 4, "rationale": "§3.7 human-in-loop, DLQ, expand-only migrations, feature flag on adaptive cadence."},
|
||
"A11y": {"score": 3, "rationale": "Targets WCAG 2.2 AA and names axe-core/keyboard/contrast — missing SR testing, user flows, cognitive-load analysis on approval task, heatmap color-only encoding risk, Law-3/Law-4 contrast verification. Meets floor, not exemplary."},
|
||
"Testability": {"score": 4, "rationale": "§7 names unit/integration/E2E/contract tests; §3.3 injectable token store; pure-core in packages."}
|
||
}
|
||
},
|
||
{
|
||
"agent": "dev",
|
||
"verdict": "REVISE",
|
||
"confidence": "high",
|
||
"summary": "Solid architecture and governance, but ship-blocker inaccuracies on LinkedIn auth (PKCE unverified) and endpoint (UGC Posts is Legacy; use Posts API with LinkedIn-Version header) must be corrected before Stage 3.",
|
||
"gaps": [
|
||
"§3.3 Auth: scopes 'openid profile email' are from 'Sign In with LinkedIn using OpenID Connect' product; 'w_member_social' requires 'Share on LinkedIn' product — must be declared per-product in Dev Portal. 'w_organization_social'/'r_organization_social' require Community Management API product approval (separate gate).",
|
||
"§3.3 Auth: PKCE is NOT documented as supported by LinkedIn's 3LO — Microsoft Learn authorization-code-flow shows only response_type=code + client_secret. Either cite PKCE support or drop it.",
|
||
"§3.3 Tools: UGC Post API is now Legacy; must use Posts API (/rest/posts) with 'LinkedIn-Version: YYYYMM' header and 'X-Restli-Protocol-Version: 2.0.0'. Building against /v2/ugcPosts = tech debt on day one.",
|
||
"§3.2/§3.5: no idempotency key on POST /content or BullMQ job dispatch — retries could double-post",
|
||
"§3.8: stargue-net/package.json is byte-identical to stargue-com/package.json (same 'name' field) — name collision to resolve before workspace link",
|
||
"§4 Stage 3.9: MCP Streamable HTTP transport is current spec (stdio+SSE deprecated); pin @modelcontextprotocol/sdk version",
|
||
"§7: no AC→Vitest mapping; contract-test recording tool unchosen (Polly.js? msw?)"
|
||
],
|
||
"principles": {
|
||
"SEBP": {"score": 4, "rationale": "Clean package boundaries; MCP client reused by scheduler; no duplicated domain logic across apps."},
|
||
"SSoT": {"score": 4, "rationale": "Zod schema + sanitize config + design tokens each single-homed. Risk: stargue-net package.json duplicates stargue-com verbatim — name collision to resolve."},
|
||
"FPT": {"score": 3, "rationale": "Plan cites repo state and doc retrieval date, but LinkedIn API claims don't match live docs: UGC Posts is Legacy (Posts API current), PKCE not documented as supported. Fix before impl."},
|
||
"DiDSP": {"score": 4, "rationale": "Authentik SSO + internal bearer + Zod + libsodium + rate limits + audit log + no public MCP exposure. Missing idempotency key to prevent replay/double-post."},
|
||
"PbD": {"score": 4, "rationale": "Low PII surface; token retention tied to lifetime; analytics capped 13mo; erasure path named."},
|
||
"OF": {"score": 5, "rationale": "Correlation IDs across API→scheduler→MCP; Pino JSON; OpenObserve; append-only audit; per-call latency+outcome; PII redaction."},
|
||
"RBR": {"score": 4, "rationale": "Approval gate + DLQ + expand-then-contract + feature flag + proactive re-auth notification."},
|
||
"A11y": {"score": 3, "rationale": "WCAG 2.2 AA + axe-core CI + keyboard parity + reduced-motion + dual-theme contrast. Lacks SR test plan and aria-live specifics."},
|
||
"Testability": {"score": 3, "rationale": "Pure core extracted; injectable clock/ID; client behind interface. Missing AC→test mapping, HTTP-recording lib, and Vitest setup for Bun workspaces."}
|
||
}
|
||
},
|
||
{
|
||
"agent": "tea",
|
||
"verdict": "REVISE",
|
||
"confidence": "high",
|
||
"summary": "Architecture is sound and SSoT/OF are exemplary; revise §4 DoDs into measurable gates, close refresh-race + idempotency + migration-rehearsal + kill-switch gaps, then re-score — currently REVISE.",
|
||
"gaps": [
|
||
"§4 Stage DoDs largely untestable as stated ('Smoke tests pass', 'Test post lands', 'End-to-end happy path on staging', 'Matches stargue.com visual system') — add measurable criteria",
|
||
"§7 omits flake controls: no frozen clock, seeded RNG, deterministic IDs, or VCR/contract-test refresh cadence",
|
||
"§3.3 refresh path lacks concurrency guard — two scheduler jobs on expired token simultaneously can double-rotate; specify SELECT FOR UPDATE / advisory lock",
|
||
"§3.5 'expand-only' migrations claimed but no shadow-column/backfill/contract sequence shown; add migration rehearsal DoD",
|
||
"§4 Stage 4 DLQ DoD (4.3) names backoff but not idempotency key — retries could duplicate public posts",
|
||
"§4 Stage 3 lacks rate-limit test DoD — add burst-21-calls test asserting 429 on #21",
|
||
"§3.6 A11y: axe-core 'baseline' and 'no violations above minor' is vague — specify ruleset version, severity gate, SR smoke",
|
||
"§3.4 sanitizer: no DoD for the private-path corpus itself — add 'CI fails if test/corpus/private/* round-trips without build error'",
|
||
"§8 gate #3 assumes Authentik exists — Stage 5.2 DoD circular if not",
|
||
"No kill-switch / feature flag for LinkedIn poster — RBR requires fast-disable independent of code deploy"
|
||
],
|
||
"principles": {
|
||
"SEBP": {"score": 4, "rationale": "Clean package/app split (§3.1); client shared between scheduler+MCP; tradeoff acknowledged."},
|
||
"SSoT": {"score": 5, "rationale": "Zod schema, sanitize config, design tokens each in one package."},
|
||
"FPT": {"score": 4, "rationale": "Cites LinkedIn docs with retrieval date (§1); stack matches verified repo state; refresh-concurrency not reasoned from first principles."},
|
||
"DiDSP": {"score": 4, "rationale": "Layered (Zod+Authentik+bearer+libsodium+audit) but idempotency + refresh-race gaps reduce from 5."},
|
||
"PbD": {"score": 4, "rationale": "Token min/encrypt/retention named; 13-month metrics retention stated. SAR path asserted, not DoD-tested."},
|
||
"OF": {"score": 5, "rationale": "Correlation IDs end-to-end, append-only audit, PII redaction, per-call LinkedIn latency capture."},
|
||
"RBR": {"score": 3, "rationale": "Approval gate, DLQ, proactive auth notify present — but expand-contract migrations unspecified, no idempotency key, no runtime kill-switch."},
|
||
"A11y": {"score": 3, "rationale": "WCAG 2.2 AA named, axe-core in CI, two-theme contrast — severity gate + SR smoke + ruleset version unspecified."},
|
||
"Testability": {"score": 3, "rationale": "Pure core extracted, injectable clock/ID/randomness, client-behind-interface — but Stage DoDs largely untestable and no determinism/record-refresh policy."}
|
||
}
|
||
}
|
||
]
|
||
}
|