The Professor: drafting agent persona for the handbook knowledge layer - #238
Conversation
Twelve-step plan grounded in the closed state of #6/#7/#8/#10/#11, ADR-0015's hybrid-authoring decision, and the persona pack spec already implemented in crates/buzz-persona. Surfaces two open calls a builder must not silently resolve: which runtime proves the drafting behaviour, and whether the proof page also becomes live handbook content. Verification: launchpad/plans/2026-08-19-issue-9-the-professor-persona.md was checked with .claude/skills/plan-issue/check-plan.sh (mechanical checks clean: one RUNS HERE, all 12 steps within the declared cap and tagged, all six named sections present). This checks plan structure only, not implementation -- there is no code to test yet. Signed-off-by: Serina Mcfall <serina.mcfall@gmail.com>
An independent review (Opus, via the review-plan skill, run in a fresh
context, not this session's earlier self-check) found 2 Blockers and 4
High findings, all verified against the real tools rather than assumed:
- step 8's findings-empty check passed on a skipped/unchecked page and on
an empty directory; now also requires skipped:[] and page_index.py's
frontmatter-completeness check, which check_provenance.py does not run
- triggers: {} does not mean "off" -- it resolves mentions back to its
built-in default of true; now set explicitly to all-false
- a GitHub rate-limit/network finding looks identical to a page defect;
step 9 now reads the message before editing anything
- MCP env-var interpolation isn't implemented yet, so step 3 no longer
claims functional end-to-end wiring, only well-formed config
- buzz pack validate doesn't check skills: paths resolve or that the
persona body is non-empty; steps 5 and 6 now assert those directly
- step 7's own worked example cited block/buzz under [launchpad], which
the real gate rejects as a prefix-repo-mismatch; fixed to [upstream]
Also fixed: check_provenance.py isn't self-contained (step 8 now gets a
handbook scripts/ checkout), buzz isn't built/on PATH in a fresh checkout
(step 1 now builds it), and steps 2/10 both claimed to own README.md
(step 10 now extends it instead of overwriting).
Verification: launchpad/plans/2026-08-19-issue-9-the-professor-persona.md
re-checked with bash .claude/skills/plan-issue/check-plan.sh after all
edits (mechanical checks clean). The substantive fixes above respond to
the independent review's evidence, not to this script.
Signed-off-by: Serina Mcfall <serina.mcfall@gmail.com>
…ed branch launchpad-26/buzz's docs/the-professor-design branch (commit 650108c0b, 2026-08-12) contains a 639-line design document for this exact issue (#9) that was never merged, never opened as a PR, and never referenced from issue #9 itself -- found only by noticing an existing git worktree on that branch while setting up isolation for this session's work. Per its own account it was independently checked by three reviewers across three passes on the block/buzz side, and traces (2026-08-12) that no runtime in this fork currently consumes resolved persona-pack configuration -- a pack is authorable and validatable but not runnable, by design rather than omission on the desktop side. Cherry-picked the single file rather than merging the branch: the branch is 42816 deletions behind current launchpad and would revert unrelated work if merged as a whole. Placed under launchpad/Research/, not launchpad/agents/ (the design's own diagram): this session's own check-models.sh hook treats any directory named agents/ as a Claude Code subagent roster and requires every top-level .md in it to carry name:/model: frontmatter -- a real naming collision the design doc didn't anticipate, since it predates that hook. launchpad/Research/ already holds this repo's other design documents (project-intelligence-layer-design.md). Verification: bash .claude/skills/plan-issue/check-plan.sh re-run on the existing plan (mechanical checks clean) as this repo's recognized suite-by-path -- there is no test suite for a markdown design document itself. Signed-off-by: Serina Mcfall <serina.mcfall@gmail.com>
Replaces the prior 12-step plan (fb18226, fixed at 30540ee) rather than patching it -- the orphaned design doc (b170781) changes the architecture enough that a diff would be less honest than a fresh plan. User-confirmed size increase to a 20-step cap: the design doc requires real new engineering (a five-tool MCP server: resolve_pin, path_exists_at, read_contract, list_categories, check_page) to avoid an LLM hallucinating commit SHAs, not just persona-pack config wiring. Key architecture changes from v1: - pack path: launchpad/agents/the-professor/, not launchpad/personas/ - tool server: launchpad/agents/the-professor/tools/ (Python, mirroring launchpad/review-agent/'s convention), NOT a new crate under crates/ -- that directory is upstream's shared workspace and buzz-dev-mcp is a generic dev-tool crate, not Professor-specific - runtime route: Route 2 (plain buzz-acp configured from environment), chosen over Route 1 (manual desktop-app recreation) because it's scriptable; Route 3 (a projector) filed as its own follow-up issue - draft destination: scratch-path proof suffices for #9's bounded acceptance; a real PR-to-handbook write path is Route-3-scale, deferred - new: launchpad/AGENTS.md section 3 gets an agents/ row (step 2), folding in the "convention" content the design doc wanted at launchpad/agents/README.md -- discovered this session that a bare .md directly in any agents/ directory trips this session's own check-models.sh hook (Claude Code subagent roster scan), so that specific file is never created Verification: bash .claude/skills/plan-issue/check-plan.sh clean (20 steps within cap, one RUNS HERE at step 3, every step tagged with done-when). Signed-off-by: Serina Mcfall <serina.mcfall@gmail.com>
…view A second independent review (Opus, fresh context, run via review-plan) found 3 Blockers and 3 High findings against the v2 replan, all verified against real tools/APIs rather than assumed: - check-models.sh actually DOES trigger on the design doc's own literal recommendation (nested .../agents/the-professor.persona.md) -- the v2 plan's own claim that it wouldn't was wrong. Fixed by naming the inner persona directory personas/, matching buzz-persona's own test fixtures and doc-comment example (pack.rs:10), not the spec's "agents/" example - Step 6's negative-control fixture (broken-01, an unsourced-behaviour- claim) isolates a judgement-engine rule check_provenance.py deliberately doesn't implement; it always returns zero findings regardless of whether check_page works. Swapped to broken-03 (prefix-repo-mismatch), a real script-engine rule, demonstrated firing live - Step 15 (now 16)'s own worked example cited this plan's own unmerged commit, which fails both pin-not-ancestor and pin-path-missing against the real gate -- a circular dependency (can't merge until the gate passes, can't pass until it cites something merged). Fixed to cite already-merged content - buzz-acp has no offline mode -- relay_url and a private key are required on its only runnable path, confirmed in lib.rs/config.rs. User decided (asked directly, given the cost) to stand up a real local relay rather than switch away from Route 2 -- added as new Step 14, cap raised 20 -> 21 - .mcp.json was never declared in plugin.json's mcp_config field, so nothing would have wired the tool server at all; buzz-acp's mcp_command takes one bare executable with no args, so the tool server needs its own shebang, not a "python3 <path>" invocation - Step 10 required an empirical drafting observation before any drafting step had run; split into a reasoned starting value (10) and the actual observation (now Step 19, after Step 16 drafts something) - Step 3's done-when asserted exactly 11 categories; the live mkdocs.yml nav has 13. Fixed to verify against the live nav at run time, not a literal count - Step 12's justification misattributed its own scratch-path choice to the design doc, which argues the opposite (only a PR reaches the gate's enforcement boundary); fixed to state the divergence explicitly - Step 13's needs tag didn't list Steps 11/12, which its own body said must precede it Verification: bash .claude/skills/plan-issue/check-plan.sh clean (21 steps within the user-confirmed cap, one RUNS HERE, every step tagged with done-when). Signed-off-by: Serina Mcfall <serina.mcfall@gmail.com>
Scaffolds launchpad/agents/the-professor/: .plugin/plugin.json (OPS manifest, personas + mcp_config declared) and a placeholder personas/the-professor.persona.md (complete identity frontmatter, model and temperature deliberately unset until STEP 10, body is a stub marker for Serina to replace at STEP 13). Persona directory is named personas/, not agents/, per the plan's own finding: a nested .../agents/*.md would trip this session's check-models.sh subagent-roster hook. Verified against crates/buzz-persona's own test fixtures (pack.rs's make_pack helper and doc comment both use personas/), not the spec's "agents/pip.persona.md" example, which is illustrative only. Verification: cargo test -p buzz-cli, run bare (no pipe/chain -- both disqualify the commit-gate's attribution check), 341 passed, 0 failed. buzz pack validate launchpad/agents/the-professor: Valid, exit 0. Signed-off-by: Serina Mcfall <serina.mcfall@gmail.com>
Add agents/ row to the launchpad/ directory table documenting persona packs for Buzz-native agents. Add explanatory paragraph clarifying the distinction between launchpad/AGENTS.md (contributor guide) and launchpad/agents/ (persona packs), with the rule that no bare .md files may sit directly in launchpad/agents/ due to the check-models.sh hook that scans that pattern as Claude Code subagent rosters. Pack documentation belongs in subdirectories (e.g., launchpad/agents/the-professor/README.md). Step 2 of 21-step implementation plan for The Professor mentor persona (issue #9). Signed-off-by: Serina Mcfall <serina.mcfall@gmail.com>
…t_categories) Step 3 of the-professor plan: launchpad/agents/the-professor/tools/ is a plain Python directory (mirroring launchpad/review-agent/'s convention, no Rust crate) holding an MCP server built on the official Python SDK (mcp.server.mcpserver.MCPServer). server.py is the executable entry point via `uv run --script` with inline PEP 723 dependencies, since no pip/pip3 exists in this environment; it exposes exactly two tools: - read_contract() fetches launchpad-26/handbook's docs/page-contract.md live via `gh api`, never quoted into source. - list_categories() parses the handbook's live mkdocs.yml nav and excludes "Home" and "The page contract" (structural entries, not PRD #4 Ruling 3 categories), leaving the eleven user-need slots. check_server.py spawns server.py as a real subprocess and talks MCP over stdio via the official client (initialize, tools/list, tools/call), verifying the running server rather than the Python functions in isolation. Confirmed live: list_categories matches a fresh independent mkdocs.yml fetch exactly, and read_contract's text contains "The claim rule". resolve_pin, path_exists_at, and check_page are later plan steps and are not built here. Signed-off-by: Serina Mcfall <serina.mcfall@gmail.com>
Step 4 of the-professor plan: resolve_pin(repo, ref) resolves a branch/tag/SHA
to its full 40-char commit SHA via `gh api repos/{repo}/commits/{ref}`, using
the same gh-CLI fetch convention as the existing handbook tools. Distinguishes
a rate-limited/unauthenticated GitHub response (HTTP 401/403/429) from a real
bad-ref/bad-repo defect, and refuses to return anything that isn't exactly 40
hex characters. check_server.py now exercises resolve_pin over stdio and
cross-checks the returned SHA against `git ls-remote` on the real repo.
Signed-off-by: Serina Mcfall <serina.mcfall@gmail.com>
Adds path_exists_at(repo, commit, path) -> bool, checking GitHub's contents API at a pinned commit via gh api, same live-fetch convention as resolve_pin. A 404 is the tool's normal negative answer; auth and rate-limit statuses are distinguished and raised instead of being misreported as "path does not exist" -- the same trap resolve_pin already guards against. check_server.py now exercises both the true and false cases over real stdio against a real pinned commit. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Signed-off-by: Serina Mcfall <serina.mcfall@gmail.com>
…provenance gate Step 6 of the-professor plan. check_page() maintains a local git checkout of launchpad-26/handbook (refreshed via fetch+reset on every call, never left to go stale) and shells out to its real scripts/check_provenance.py and scripts/page_index.py as subprocesses against an isolated per-call scratch directory -- never reimplementing the gate's rules in Python, per the design doc's own answer to open question 5. check_server.py now exercises check_page over real stdio with two real handbook fixtures: compliant.md (zero findings, zero skipped) and broken-03-prefix-repo-mismatch.md (a prefix-repo-mismatch finding). broken-01 is deliberately not used as a negative control since it isolates a judgement-engine rule the script does not implement. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Signed-off-by: Serina Mcfall <serina.mcfall@gmail.com>
Creates launchpad/agents/the-professor/.mcp.json with professor-tools MCP server configuration, pointing to the tools/server.py executable entry script. The server reads GitHub auth from gh CLI's environment (no explicit GITHUB_TOKEN env var needed). Verified: - buzz pack validate: OK - buzz pack inspect: reports 1 MCP server - tools/check_server.py: ALL CHECKS PASSED Signed-off-by: Serina Mcfall <serina.mcfall@gmail.com>
Teaches the agent to call read_contract, list_categories, resolve_pin, path_exists_at, and check_page rather than recalling any of their answers from memory, and to tag behaviour claims with an origin prefix while attributing opinion claims to the persona's author field, never both for the same claim. Signed-off-by: Serina Mcfall <serina.mcfall@gmail.com>
Add to the-professor persona frontmatter:
- skills: ["./skills/draft-page/"]
- subscribe: [] (explicit empty)
- triggers: {mentions: false, keywords: [], all_messages: false} (all explicit)
This ensures the persona explicitly references the skill written in Step 8
and disables all trigger modes with fully-explicit config (not relying on
built-in defaults).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: Serina Mcfall <serina.mcfall@gmail.com>
Step 10 of the-professor plan: add model and temperature to the persona frontmatter, and document the reasoning in a new README. Temperature (0.4) is a starting value reasoned from the design doc's tension between factual-synthesis accuracy and consistent voice, not yet validated against a real drafting attempt -- that empirical check is Step 19. buzz pack validate launchpad/agents/the-professor still exits 0. Signed-off-by: Serina Mcfall <serina.mcfall@gmail.com>
… 11) Add "Runtime Route" section to launchpad/agents/the-professor/README.md explaining why Route 2 (plain buzz-acp configured from environment) was chosen over Route 1 (desktop app GUI) and Route 3 (projector). Clearly states the specification-vs-configuration distinction and notes that Route 3 is filed separately as Step 20. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Signed-off-by: Serina Mcfall <serina.mcfall@gmail.com>
… drafts sufficient for #9 Extend launchpad/agents/the-professor/README.md with a new section documenting that for #9's bounded acceptance criteria, a scratch-path draft (stored in a temporary directory, not committed to the handbook, not opened as a PR) is sufficient. Section explicitly states this diverges from the design doc's own conclusion and explains why the divergence is acceptable: #9's acceptance does not require a PR, and write-permission decisions belong to a separate issue. Also add a new subsection confirming that all five tools in the tool server (read_contract, list_categories, resolve_pin, path_exists_at, check_page) are read-only — no writes, pushes, commits, or PR creation against GitHub or the handbook repository. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Signed-off-by: Serina Mcfall <serina.mcfall@gmail.com>
Replaces the <!-- VOICE --> stub with the actual persona prompt, drafted collaboratively with Serina in chat (not by an implementer subagent, per issue #9's explicit exclusion and the standing rule that creative/voice content is hers to write). Identity: The Professor's own discipline (a citation is owed, not assumed) merged with an investigator's behavioral DNA (don't stop at the first plausible answer, treat contradictions as interesting, know verified-fact from inference). Voice: dry, quietly amused by a bad citation, restrained on serious findings. Scoped explicitly to what the pack's five tools can actually do today -- no narrated capabilities (Slack, live systems, arbitrary search) it doesn't have. The claim rule gets its own section: wit is channeled through two separate sentences (sourced claim, then attributed opinion), never blended into one, so the persona's voice can't accidentally produce a page the gate rejects. Verification: buzz pack validate exits 0 (Valid.); buzz pack inspect reports a 4046-char System prompt (was the stub before); the tool server's own check_server.py -- all five tools exercised live -- passes unchanged (this step touched only the persona body, not the tools). Signed-off-by: Serina Mcfall <serina.mcfall@gmail.com>
anthropic:claude-sonnet-4-20250514 404s against the live API (not a valid model id on this account); discovered during the real Step 16 drafting attempt. Switched to anthropic:claude-sonnet-5. Signed-off-by: Serina Mcfall <serina.mcfall@gmail.com>
…haviour Credential Policy: each person running The Professor locally brings their own key (BYOK) for Route 2 -- no shared cohort secret, distinct from ADR-0012's upstream-synthesis CI credential which has a different blast radius. Notes the Route 3 carve-out explicitly. Observed Behaviour: the real Step 16 drafting attempt validated the temperature-0.4 starting choice (accurate, honestly-scoped prose) and surfaced a genuine capability gap -- goose had no write/shell tool wired in, so it composed and validated a clean draft but could not save or reply itself. The controller extracted and placed the already-validated draft by hand. This is why Route 3 needs to define a real write path before the pack is load-bearing. Signed-off-by: Serina Mcfall <serina.mcfall@gmail.com>
…ssor-persona Signed-off-by: Serina Mcfall <serina.mcfall@gmail.com>
benmitchell11
left a comment
There was a problem hiding this comment.
Read the full diff, with server.py as the priority given the PR's own escalation #5 flags it as never having gone through review-code before this PR opened.
Security review of server.py (all 5 MCP tools), done independently:
- Confirmed read-only end to end:
read_contract/list_categories/resolve_pin/path_exists_atare all singlegh apiGET calls;check_pagedoesgit fetch+reset --hardagainst a local temp-dir cache of the handbook (not the real repo) and shells out to the handbook's owncheck_provenance.py/page_index.pyread-only. No push/commit/write call to GitHub or the handbook repo anywhere in the file. - All
subprocess.runcalls use list-form argv, nevershell=True—repo/ref/path/commitargs can't achieve shell injection regardless of content. - The rate-limit/auth-vs-404 disambiguation in
resolve_pin/path_exists_at(checking the JSONstatusfield rather than trusting exit code or falling through) is a real, non-obvious trap correctly avoided — an ordinary 403 could otherwise silently masquerade as "no such ref." _subprocess_env_with_github_token()never logs or returns the token, only places it in the env dict handed to the gate-script subprocess — checked this holds.
Spot-verified, not just trusted:
- Both pinned SHAs from the Step 16 drafting attempt are real:
crates/buzz-persona/PERSONA_PACK_SPEC.mddoes exist at9a1e861a...inblock/buzz(main has since moved, which is expected — a pin is point-in-time by design, not a claim that it's still HEAD). The launchpad pin (45dd387e3...) is the same commit I independently verified as accurate while reviewing buzz-infrastructure#69. #237,#239,#240all exist and match what's described. The--no-verifymobile-test bypass is justified — zeromobile/**files in this diff, issue filed first, same precedent as#181.check_server.py's test design is genuinely careful — it deliberately avoids usingbroken-01as a negative control because that fixture would pass regardless of whethercheck_pageworks (checked this reasoning againstcheck_provenance.py's own documented judgement-vs-script engine split), and checks both the true and false case forpath_exists_atrather than only the happy path.
One new finding, not covered by #240: personas/the-professor.persona.md's frontmatter description ("A mentoring persona designed to guide learning through questions and insights.") and the identical copy in .plugin/plugin.json don't match what the persona actually is. The body defines a strict citation-discipline drafting agent scoped to exactly five named tools ("You do not have access to Slack, live systems, or arbitrary search") — nothing about guiding learning through Socratic questions. Reads like unedited template boilerplate carried over from scaffolding, and it's user-facing (plugin listings, marketplace description). #240 only covers the Model section and Observed Behaviour path — this is a third, separate doc-accuracy gap worth folding into that follow-up or its own tiny issue.
Not blocking — approving. The security-critical piece (server.py) checks out independently, and this is a one-line description fix, not a functional defect.
…lure in server.py
Found by an independent serina:review-code pass (the plan's own required
gate for this file, run late after being missed during the original build):
- Blocker: path_exists_at built its gh api request as
f"repos/{repo}/contents/{path}?ref={commit}" -- a hand-concatenated
query string. A `path` value containing its own "?ref=..." could inject
a second ref parameter and get checked against that instead of the
pinned commit the caller asked about, defeating the tool's whole
purpose. Fixed by passing ref via `gh api -f`/`--method GET` instead of
string concatenation, and by rejecting any `path` containing '?' or '&'
outright as invalid input.
- Low, same function: `commit` was never validated as a real 40-hex SHA
(resolve_pin validates its own output this way; path_exists_at accepted
anything, including a branch name). Now validated up front.
- High: check_page only checked whether check_provenance.py's stdout was
parseable JSON, never whether the script itself exited non-zero. A
failed gate run that still printed valid-but-empty JSON would have been
reported as "zero findings" -- a clean pass that wasn't one. Now a
non-zero exit with no findings/unchecked/skipped raises instead of
passing through silently.
- Medium: the local handbook checkout lived at a fixed, predictable path
under the shared system temp directory, trusted without checking who
created it -- another local process could plant a hostile checkout
there ahead of time. Now checked for ownership before being trusted;
anything not owned by this process is wiped and re-cloned fresh.
Verified: check_server.py's full fixture suite passes (including the real
vs. fabricated path_exists_at cases and both check_page fixtures), plus a
new targeted check confirming the exact injection payload from the review
and a bare branch name both now raise instead of silently succeeding.
Signed-off-by: Serina Mcfall <serina.mcfall@gmail.com>
README.md's Model section still named claude-sonnet-4-20250514, contradicting its own Observed Behaviour section which narrates discovering that model's 404 and switching to claude-sonnet-5 (commit 6d585da) -- and contradicting personas/the-professor.persona.md's actual model: field. Updated to match. The Observed Behaviour section said Step 16's validated draft was "placed at the scratch path by hand" without naming it. Step 19 of the original plan required this exact path as evidence; it existed only in the git-ignored SDD ledger, invisible to any reviewer of this README. Now states it literally: /tmp/the-professor-scratch-drafts/persona-packs-upstream-spec-fork-local-tooling.md (confirmed against the ledger before writing it here, not recalled from memory). Verified: cargo test -p buzz-core passes (251 + 2 doctests, 0 failed) -- unaffected by this doc-only change, run to confirm nothing else broke. Signed-off-by: Serina Mcfall <serina.mcfall@gmail.com>
Summary
Adds The Professor: a persona pack that drafts handbook pages, wired to a real 5-tool MCP server (read-only) against the handbook's own provenance contract, plus a real drafting attempt that produced and gate-validated a two-claim page end-to-end.
Related issue
Closes #9
Issue type
Task
Agent provenance
Objective
A
.persona.mdpersona pack for The Professor, wired to a read-only MCP tool server against the handbook's page contract, proven against a real drafting attempt.Impacted components
launchpad/agents/the-professor/personas/the-professor.persona.md
launchpad/agents/the-professor/tools/server.py
launchpad/agents/the-professor/tools/check_server.py
launchpad/agents/the-professor/skills/draft-page/SKILL.md
launchpad/agents/the-professor/.mcp.json
launchpad/agents/the-professor/.plugin/plugin.json
launchpad/agents/the-professor/README.md
launchpad/AGENTS.md
launchpad/Research/the-professor-design.md
launchpad/plans/2026-08-19-issue-9-the-professor-persona.md
Approach and rejected alternatives
Built a real MCP tool server (official
mcpPython SDK,uv run --scriptshebang) rather than trusting the model to resolve pins/paths from memory — the design doc's own identified SHA-hallucination risk. Route 2 (plainbuzz-acpconfigured from the environment) was chosen over Route 1 (recreating the desktop app's config, not scriptable/testable) — see README's Runtime Route section for the full reasoning, including the explicit specification-vs-configuration caveat. Scratch-path draft output was chosen over a real write-path/PR-against-handbook flow, since #9's bounded acceptance does not require one and the design doc names that as a separate, deferred decision (Open Question 2, Route 3).Verification
Command run:
Raw output (tail):
Command run:
Raw output:
This re-verifies (independent of the ACP session transcript) the exact draft content The Professor produced during the real Step 16 drafting attempt: two claims,
[upstream] block/buzz@mainpinned to9a1e861ab8507ee28e5f03010b7f36d1b74ec919(crates/buzz-persona/PERSONA_PACK_SPEC.md), and[launchpad] launchpad-26/buzz@launchpadpinned to45dd387e316bdd2ad677ce6bcc1d742baec53532(launchpad/AGENTS.md§3,launchpad/review-agent/'s existence) — both resolved viaresolve_pinagainst the real GitHub API, not recalled from memory.Command run:
Raw output (tail):
4 pre-existing failures, all in accessibility large-text-size timestamp layout tests (
compose_note_page_test.dart,forum_widgets_test.dart,note_card_test.dart) — filed as #237, unrelated to this PR's diff (zeromobile/**files touched). Pushed past this hook with--no-verifyon Serina's explicit authorization, with #237 filed first as the paper trail (same category of problem as the precedent at #181).Command run:
Raw output:
Confirms the exact exploit
review-codeidentified (apathvalue smuggling its own?ref=to override the pinnedcommit) is now rejected outright, and that a bare branch name incommit(instead of a real pin) is also rejected.Not verified
Desktop and relay-integration behavior beyond what
just test-unit/cargo test -p buzz-clicover — this PR touches no desktop or relay code, so those suites were not re-run beyond the pre-push hook's own pass. Whether the persona pack format itself (frontmatter shape, MCP wiring) is stable against upstreamPERSONA_PACK_SPEC.mdchanges going forward — only checked against the pinned commit resolved during Step 16. Whether goose'sGOOSE_TEMPERATURE/GOOSE_MODELenv-var projection is the correct mechanism long-term, versus a config file — noted in the README's Runtime Route section as an open, stated caveat rather than something this PR resolves.Security implications
All five MCP tools in
server.pyare confirmed read-only (no write, push, or commit calls to GitHub or the handbook repo) — independently re-verified by direct code inspection, not just trusted from the implementation report. The credential used to run the agent (an Anthropic API key) is bring-your-own-key per person, documented explicitly in the README's Credential Policy section, and was never read or printed by the controller during setup — only copied between environment variables. No secret, key, or hostname was added to any tracked file.Escalations
--no-verifyon Serina's explicit authorization after filing that issue first.launchpad-26/handbooknav. The draft's own body states it is a scratch/test artifact, not real handbook content, and the save step above was not fully autonomous.review-finalfindings — a full-branch review was run before this PR left draft. One Blocker: the plan's ownreview-codegate onserver.py(real security surface —gh apisubprocess calls, gate-script invocation) was never dispatched during the original build. Two Medium findings (a stale model ID in README's Model section contradicting the persona file, and Step 19's required exact scratch-path evidence missing from the README) were deferred as their own follow-up: task: fix two README documentation-accuracy gaps for The Professor #240.review-coderesults (the Blocker from # prd-03 — reproducible and hardened Buzz deployment: rebuild the cohort server from a bare Ubuntu host #5, resolved) — dispatched againstserver.py, independent of the branch's author. Found real issues, now fixed inc55272389: a Blocker —path_exists_atbuilt itsgh apirequest as a hand-concatenated query string, so apathvalue containing its own?ref=...could override the pinnedcommitit was supposed to check against, defeating pin verification entirely; fixed by passingrefviagh api --method GET -fand rejecting anypathcontaining?/&outright. A High —check_pagenever checked whethercheck_provenance.pyitself exited non-zero, only whether its stdout parsed as JSON, so a failed gate run printing valid-but-empty JSON would report as a clean pass; fixed to raise when a non-zero exit carries no findings/unchecked/skipped. A Medium — the local handbook checkout lived at a predictable, world-writable/tmppath trusted without an ownership check; fixed to verify ownership and wipe-and-reclone if it doesn't match. A Low —path_exists_at'scommitargument was never validated as a real SHA the wayresolve_pin's output is; now validated up front. Re-verified againstcheck_server.py's full fixture suite plus a new targeted check confirming the exact injection payload and a bare branch name both now raise.