phpbotscout: delivery plan¶
- Authors
- Matt Cockayne, Claude Opus 5 (AI drafting assistant)
- Date
- 2026-07-26
- Status
- APPROVED — 2026-07-27
- Implements
- Delivery sequencing for phpbotscout: draft requirements (APPROVED) and phpbotscout: interface design (APPROVED).
1. Purpose¶
The requirements define twelve phases. This document plans them: what depends on what, what can run in parallel, what can start before any code is written, where each phase lands, and what could stall the critical path.
It deliberately contains no dates. This is one maintainer working to no external deadline; invented dates would be fiction that later gets treated as commitment. Phases carry relative size (S/M/L) and a dependency order, which is what actually governs sequencing.
How to work this plan¶
- No phase begins without an approved spec. The requirements are approved; each phase still
needs its own implementation spec citing the
R-*identifiers it satisfies, per AGENTS.md. - A phase is done when its exit criterion is met — not when the code is written. Several exit criteria are measurements, not merges.
- Every phase that touches user-facing behaviour enters shadow mode before it acts (R-OPS-7).
2. The dependency graph¶
┌─────────────────────────────────────────────────────────────────┐
│ TRACK 0 — no dependencies, start immediately │
│ Discord app + privileged intent · corpora · policy · test guild│
└───────────────┬─────────────────────────────────────────────────┘
│ (intent approval gates every Discord phase)
▼
┌──────────────────────────────┐ ┌──────────────────────────────┐
│ TRACK A — Discord │ │ TRACK B — retrieval │
│ │ │ │
│ 1a-0 library spike [S] │ │ 2 index + ask CLI [L] │
│ ↓ (upstream repos) │ │ ↓ │
│ 1a chat-platform×2 [L] │ │ 2b provider benchmark [M] │
│ ↓ (upstream, released) │ │ ↓ │
│ 1b Discord ingest [M] │ │ 2c hybrid spike [S] │
└───────────────┬──────────────┘ └───────────────┬──────────────┘
│ │
└─────────────┬─────────────────────┘
▼
┌────────────────────────┐
│ 3 answering [M] │ ← the first user-visible phase
└───────────┬────────────┘
│
┌────────────────────────┼────────────────────────┐
▼ ▼ ▼
┌──────────────┐ ┌────────────────┐ ┌────────────────┐
│ 4a forge │ │ 5 moderation │ │ 4c gap │
│ issues[M] │ │ [L] │ │ explorer[S] │
│ (upstream) │ │ (needs 1b + │ │ (needs 4b) │
└──────┬───────┘ │ mod capability│ └────────────────┘
▼ │ in provider) │
┌──────────────┐ └────────────────┘
│ 4b escalation│
│ [M] │
└──────┬───────┘
▼
┌──────────────┐
│ 6 feedback │
│ loop [M] │
└──────────────┘
The finding that should change the order¶
Track B has no dependency on Track A. The knowledge index and the ask CLI need no Discord
code, no upstream modules, and no privileged-intent approval —
R-CLI-1 exists precisely so the retrieval path can be
exercised from a terminal.
That matters more than it first appears, because the two tracks have opposite risk profiles:
| Track A (Discord) | Track B (retrieval) | |
|---|---|---|
| Largest unknown | Which library. Contained by the contract, reversible | Whether retrieval answers real questions well |
| Lead time | Privileged intent approval, plus two upstream module releases | None |
| If it goes badly | Swap the provider | The product premise is weak |
| Value before it completes | None | ask is useful to the owner on day one |
Track B carries the product's largest genuine unknown and none of its lead time. Track A carries the lead time and a well-contained unknown.
Recommendation: run both, but if only one can progress, progress Track B. Numbering the phases 1 → 6 implies Discord comes first; the dependency graph says it need not, and the risk profile says it should not. Discovering that BM25 over your corpus answers 27 of 30 real questions — or only 11 — is the single most valuable thing that can be learned early, and it can be learned without writing a line of Discord code.
3. Track 0 — start now, no code required¶
None of this depends on anything, and all of it unblocks later phases.
Correction — 2026-07-27
An earlier revision of this plan claimed 0.1 carried external lead time, on the belief that
the MESSAGE_CONTENT privileged intent required Discord's approval. It does not, at this
scale. Since June 2026 the review threshold is 10,000 unique users across all installs —
not the older 100-server rule — and below it privileged intents are a toggle in the Developer
Portal with no application to file.
phpbotscout will run in one guild. It is a checkbox, not a queue.
The sequencing conclusion in Q-D1 is unchanged, but one of its supporting arguments was false. Track A's real serialisation is the two upstream module releases, not Discord's approval process — which makes the case for Track B primary rest on the risk profile rather than on lead time.
| # | Task | Why now | Unblocks |
|---|---|---|---|
| 0.1 ✅ | Register the Discord application, enable the MESSAGE_CONTENT and SERVER MEMBERS intents, and install it to the test guild with a minimal permission set |
No approval needed below 10,000 users. Still first, because everything in Track A needs the token and the app identity | 1b, 3, 5 |
| 0.2 ✅ | Create a dedicated test guild | Integration tests must never run against the live server (§13) | 1b onward |
| 0.3 ◐ | Assemble the benchmark corpus — ≥30 real questions with known-good answers and expected citations (R-OPS-13) | It is pure writing, and Phases 2, 2b and 2c are all measured against it | 2, 2b, 2c |
| 0.4 ✅ | Assemble the adversarial prompt-injection corpus — test/corpus/adversarial.yaml; surfaced a corpus-trust gap |
Must exist before Phase 4 ships; cheaper to write calmly now | 3, 4b, 5 |
| 0.5 ⏳ | Assemble the moderation test corpus — real and synthetic messages, including hard negatives | The tuning harness for R-MOD-13 | 5 |
| 0.6 ◐ | Draft the public policy — docs/about/policy.md; one placeholder blocks completion (AI provider, decided at 2b) — what the bot reads, logs, retains, and how to appeal (R-GOV-1, R-MOD-21) |
A user-facing requirement, not documentation. Must be published before the bot speaks | 3 |
| 0.7 ✅ | Enumerate the corpus sources the predicate currently matches, and confirm the exclusions hold — see validation notes | Validates R-KB-1 against reality before code depends on it | 2 |
0.3 is the load-bearing one. Three phases are measured against that corpus, and a benchmark built from invented questions measures nothing. Draw them from real support history — things actually asked in Discord, in issues, or by you at 22:40 — and include questions the corpus genuinely cannot answer, because R-KB-8 needs the failure path tested as hard as the success path.
4. Phase detail¶
Each phase lists where it lands, what spec it needs, and what "done" means. Sizes are relative: S ≈ a focused sitting, M ≈ a substantial piece of work, L ≈ the biggest units here.
Phase 1a-0 — Discord library spike · S · upstream¶
Lands in: a scratch worktree; artefacts discarded (R-ARCH-14).
Spec: none — the rubric in §3.3 is the spec.
Does: the same vertical slice — connect, receive on an allowlisted channel, post a threaded
reply — against disgo, discordgo and arikawa, behind a draft contract.
Exit: rubric scored; decision record written naming the rejected options and why; the draft
contract revised where the spike exposed a bad boundary.
Remember the second objective: this validates the contract, not just the library. Contract changes forced by the spike are the point, not scope creep.
Phase 1a — chat-platform + chat-platform-discord · L · upstream¶
Lands in: two new repositories under gitlab.com/phpboyscout/go/.
Spec: required, in the module repo — contract surface, registry, conformance harness.
Does: the contract with its read/act split (R-ARCH-10),
the optional moderation capability (R-ARCH-11), the
depfootprint guard, and the Discord provider on the chosen library.
Exit: both modules released; conformance suite green; depfootprint proves no vendor SDK in the
contract module.
The largest single unit in the plan, and the one furthest from this repository.
Phase 1b — Discord ingest · M · phpbotscout¶
Needs: 1a released, 0.1 approved, 0.2.
Does: consume the modules, register the gateway as a controls-managed service, read-only
ingest with channel allowlisting. Posts nothing.
Exit: the bot connects, logs messages from allowlisted channels, survives a forced disconnect
with session resume, and shuts down gracefully.
Phase 2 — knowledge index + ask CLI · L · phpbotscout¶
Needs: 0.3, 0.7. Nothing from Track A.
Does: source discovery against the predicate, the SQLite FTS5 index, incremental refresh, the
Retriever contract, and the index and ask commands.
Exit: ask answers benchmark questions from the terminal with correct citations, and
index sources explains why each source qualified or was excluded.
The most valuable phase in the plan. It answers the product's central question and delivers something usable before any bot exists.
Phase 2b — provider benchmark · M · phpbotscout¶
Needs: 2, 0.3, 0.4. Does: run candidates through the identical retrieval path, scored on the seven-point rubric. Exit: rubric scored, decision record written, per-task provider selections made, and the benchmark committed so it can be re-run when a model is deprecated or a price changes.
Phase 2c — hybrid-retrieval spike · S · phpbotscout¶
Needs: 2, 0.3. Does: classify keyword-only failures by whether semantic search would plausibly have fixed them; estimate effort only if benefit is demonstrated. Exit: decision record with an explicit build-now / build-later / never verdict and the benefit figure behind it.
Phase 3 — Discord answering · M · phpbotscout¶
Needs: 1b, 2, 2b, 0.4, 0.6. The first phase users see.
Does: triggers, threaded answers with citations, hedged confidence, feedback reactions, /ask
and /docs with their visibility split, rate limiting, and the injection defences.
Exit: 30 shadow answers reviewed for quality and citation accuracy (Q-D2),
then live. Spend measured, so the R-OPS-3a ceiling can
finally be set from evidence.
Publish the policy (0.6) before this goes live, not after.
Phase 4a — forge issue capability · M · upstream¶
Needs: nothing. Can run in parallel with everything.
Lands in: go/forge and go/forge-gitlab.
Does: the optional IssueProvider capability, the GitLab implementation, conformance coverage.
Exit: both released; no existing provider broken; a provider without the capability fails the
type assertion cleanly.
Phase 4b — GitLab escalation · M · phpbotscout¶
Needs: 3, 4a released. Does: the consent modal, deduplication with subscription, project routing, mention neutralisation with its adversarial tests, back-links, and scoped relay. Exit: issues filed from Discord with full context, zero mention leakage, and a closure relayed back to the asker with its reason.
Phase 4c — gap explorer · S · phpbotscout¶
Needs: 4b. Does: cluster unanswered and low-confidence questions; browsable TUI ranked by frequency and subscription count. Exit: the report drives one real documentation change. That is the exit criterion — not that the screen renders.
Phase 5 — moderation · L · phpbotscout + upstream¶
Needs: 1b, 0.5, and the moderation capability in chat-platform-discord
(R-ARCH-11a).
Adversarial coverage still to add (held here rather than in the published corpus, since a list
of untested vectors is more useful to an attacker than to a reader):
- multi-turn injection — a payload split across several messages, harmless individually
- retrieval-shaped injection — a question crafted so the retrieved context is itself the attack
- locale and encoding — RTL overrides, zero-width joiners, homoglyphs, in both Discord content and forge payloads
- moderation evasion — content designed to pass the ruleset while still violating it, the inverse of the existing cases
Does: the layered rule engine with bounded regex, the categories including the distinct self-harm path, the private mod queue with its report cards and action buttons, the audit trail, appeals, and the enforcement path with its hard separation from anything LLM-influenced. Exit: false-positive rate below 5% across at least 50 detections, pre-registered before measurement (Q-D3), then graduated to live with the narrowest viable permission grant. If the bar is not met, moderation remains advisory permanently — a legitimate terminal state, not a failed phase. The self-harm category is excluded from this gate and tuned for recall instead.
The most safety-critical phase, and the only one that can harm someone if it is wrong.
Phase 6 — feedback loop · M · phpbotscout¶
Needs: 4b, 4c, 5. Does: knowledge-gap reporting at scale, digests, release announcements, the MCP tool surface, and the dashboard if it is still wanted.
Also, when the public microsite goes up: run the brand-a-project pipeline for the flat
house-style emblem, favicon, branding page and estate-page entries. The Discord avatar
(docs/assets/brand/avatar-1024.png) is a character, deliberately — it reads as a member in a
member list. It does not replace the flat emblem that logo-config and logo-forge share, which
is what the docsite, favicon and GitLab project avatar want. Two marks, two jobs.
Exit: the gap report is used routinely rather than admired once.
Revisit Q-I7 here: if --output json piped into something existing has
been covering it, do not build the dashboard.
5. Critical path¶
0.1 ──▶ 1a-0 ──▶ 1a ──▶ 1b ─────────────┐
├──▶ 3 ──▶ 4b ──▶ 6
0.3 ──▶ 2 ──▶ 2b ───────────────────────┘ ▲
│
4a ──────────────────────────────────────────────┘
The longest chain runs through Track A: intent approval → spike → two upstream module releases → ingest → answering. Four of those five are prerequisites for a phase that has not started.
Three consequences worth acting on:
- 0.1 first, though it is quick — everything in Track A needs the app identity and token, and nothing in Track A can be tested without an install on the test guild.
- 4a whenever there is slack. It blocks Phase 4b, depends on nothing, and is the natural thing to pick up while waiting on something else.
- Track B is the hedge. If Track A stalls — intent rejected, library spike inconclusive — Track
B still delivers a working
askcommand and answers the question that actually matters. - Sequencing is settled by Q-D1: 0.1 immediately, 1a-0 early, then Track B carries the main effort with 1a picked up in the gaps.
6. Risks¶
| Risk | Effect | Mitigation |
|---|---|---|
| — | ✅ Not a risk at this scale. Below 10,000 users the intent is a Developer Portal toggle. It becomes a real risk only if the bot crosses that threshold, at which point review applies and access must be reapplied for annually | |
| Shadow mode has no traffic to observe | Would make a time-based exit criterion meaningless | ✅ Resolved by Q-D2: exit counts reviewed answers, not elapsed days, so a quiet server takes longer but still yields a real signal |
| Upstream release cadence serialises the work | 1b waits on 1a, 4b waits on 4a | Develop against a replace directive; require released versions only at phase exit. Development need not wait on a release, only merging does |
| Benchmark corpus is weak | 2b and 2c measure nothing, and the wrong provider is chosen confidently | Draw 0.3 from real support history; include genuinely unanswerable questions; have someone else sanity-check the known-good answers |
| Moderation false-positive rate never converges | Phase 5 cannot graduate out of shadow mode | ✅ Resolved by Q-D3: advisory-only is a pre-agreed legitimate outcome, and the 5%/50-detection bar is registered before measurement so it cannot be rationalised down |
| Single maintainer, every phase self-reviewed | Blind spots persist; specs approve themselves | Keep phases small enough to review honestly. Use the adversarial corpora as an external check, since they are written before the code and cannot be rationalised after it |
| Scope creep from the deferred decisions | 2c returns "build", and hybrid retrieval quietly becomes a phase of its own | 2c produces an effort estimate before any commitment, and "build later" is an explicitly permitted verdict |
7. What v1 looks like¶
v1 is complete when a member can ask a question in a public channel and get a cited answer; when a question the corpus cannot answer becomes a GitLab issue with their consent and their context, and they hear back when it resolves; when a moderator has a queue with evidence, an audit trail, and an appeal path; and when the questions the bot could not answer arrive as a ranked list of documentation to write.
Everything in §11 of the requirements — digests, announcements, FAQ synthesis, onboarding — is post-v1 and should stay there.
8. Resolved questions¶
Q-D1 — How the tracks interleave. ✅ Resolved 2026-07-27. Track B is primary, with two Track A items pulled early: 0.1 immediately (external clock), and 1a-0 soon after (small, and it produces a decision). The 1a upstream module work is then the natural thing to pick up while waiting on a benchmark run or a review.
The reasoning is not purely technical. Solo projects stall for lack of visible progress, and a
working ask command is genuinely useful and completely invisible. Running the spike early buys an
early concrete result without letting Discord's lead time set the pace of everything else.
Q-D2 — What gates Phase 3. ✅ Resolved 2026-07-27. The exit criterion changes from one week in shadow mode to 30 shadow answers reviewed for quality and citation accuracy.
Elapsed time was the wrong unit: it measures the calendar and assumes traffic. A count is robust in both directions — a busy server clears it in days, a quiet one takes longer but still produces a real signal rather than an expired timer. The Phase 2b benchmark remains the primary quality gate; shadow mode confirms behaviour in the wild, which is a different question from whether the answers are good.
Q-D3 — The bar for moderation enforcement. ✅ Resolved 2026-07-27. The threshold is pre-registered before measurement: a false-positive rate below 5% across at least 50 detections. If it is not met, moderation remains advisory permanently, and that is a legitimate terminal state rather than a failure.
Setting the number before there is sunk cost in the ruleset is the entire mechanism. A threshold chosen after seeing the results is a threshold chosen to be met.
The self-harm category is excluded from this gate¶
Not an exception to be argued for later — a different thing being measured.
Every other category treats a false positive as the harm to guard against. Self-harm inverts it: a false positive offers someone support they did not need, which is mildly awkward; a false negative misses someone in crisis. That category is therefore tuned for recall, accepting more false alarms — the opposite posture to the rest of the ruleset.
It also never enforces. Self-harm detections route to support resources and moderator awareness (R-MOD-4), never to delete, timeout or ban, and are excluded from the notification and appeals flow entirely (R-MOD-20). Folding it into a "can we enforce automatically" threshold would conflate two unrelated action classes and would pull the category's tuning in exactly the wrong direction.
9. Open questions¶
None. Sequencing and both graduation gates are settled; the plan's remaining unknowns are the deferred measurements already recorded in the requirements.