Skip to content

test(pmoves-yt): add endpoint tests - #12

Merged
POWERFULMOVES merged 1 commit into
mainfrom
codex/add-tests-for-yt-endpoints-with-mocks
Sep 16, 2025
Merged

test(pmoves-yt): add endpoint tests#12
POWERFULMOVES merged 1 commit into
mainfrom
codex/add-tests-for-yt-endpoints-with-mocks

Conversation

@POWERFULMOVES

@POWERFULMOVES POWERFULMOVES commented Sep 14, 2025

Copy link
Copy Markdown
Owner

Summary

  • add FastAPI endpoint tests for pmoves-yt service
  • mock external services (yt_dlp, S3, ffmpeg-whisper, Hi-RAG)
  • assert Supabase inserts/updates and event emissions

Testing

  • pytest pmoves/services/pmoves-yt/tests/test_endpoints.py -q

https://chatgpt.com/codex/tasks/task_b_68c613e88aac8324a699a258d8626aa4

Summary by CodeRabbit

  • Tests
    • Added comprehensive end-to-end tests for YouTube-related endpoints (info, download, transcript, summarize, emit).
    • Validates HTTP responses and key side effects while isolating external integrations via mocks.
    • Improves reliability, prevents regressions, and documents expected behaviors across common workflows.
    • Enhances confidence in critical paths without performing real network or file operations.

@coderabbitai

coderabbitai Bot commented Sep 14, 2025

Copy link
Copy Markdown
Contributor

Walkthrough

Adds a new test module for pmoves-yt endpoints using FastAPI TestClient and monkeypatching. It tests /yt/info, /yt/download, /yt/transcript, /yt/summarize, and /yt/emit, validating HTTP responses and mocked side effects for external services (YoutubeDL, S3 upload, Supabase ops, event publishing, and external POSTs).

Changes

Cohort / File(s) Summary
Endpoint tests for pmoves-yt
pmoves/services/pmoves-yt/tests/test_endpoints.py
New test suite covering info, download, transcript, summarize, and emit endpoints; uses TestClient and monkeypatching for YoutubeDL, S3 upload, Supabase insert/update/get, event publishing, and external HTTP calls; asserts HTTP 200 responses and specific side-effect counts/arguments.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

I thump my paw on passing tests,
Hop-hop through endpoints’ subtle quests.
Mocked winds blow, no I/O stress—
S3, Supa, events coalesce.
One carrot raised for green-lit nights,
The burrow hums with tidy bytes.
☁️🥕✅


📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Free

📥 Commits

Reviewing files that changed from the base of the PR and between f3607a3 and ddb366e.

📒 Files selected for processing (1)
  • pmoves/services/pmoves-yt/tests/test_endpoints.py (1 hunks)

Tip

👮 Agentic pre-merge checks are now available in preview!

Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.

  • Built-in checks – Quickly apply ready-made checks to enforce title conventions, require pull request descriptions that follow templates, validate linked issues for compliance, and more.
  • Custom agentic checks – Define your own rules using CodeRabbit’s advanced agentic capabilities to enforce organization-specific policies and workflows. For example, you can instruct CodeRabbit’s agent to verify that API documentation is updated whenever API schema files are modified in a PR. Note: Upto 5 custom checks are currently allowed during the preview period. Pricing for this feature will be announced in a few weeks.

Please see the documentation for more information.

Example:

reviews:
  pre_merge_checks:
    custom_checks:
      - name: "Undocumented Breaking Changes"
        mode: "warning"
        instructions: |
          Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal).

Please share your feedback with us on this Discord post.


Note

🎁 Summarized by CodeRabbit Free

Your organization is on the Free plan. CodeRabbit will generate a high-level summary and a walkthrough for each pull request. For a comprehensive line-by-line review, please upgrade your subscription to CodeRabbit Pro by visiting https://app.coderabbit.ai/login.

Comment @coderabbitai help to get the list of available commands and usage tips.

@POWERFULMOVES
POWERFULMOVES merged commit 2ab93c3 into main Sep 16, 2025
1 of 3 checks passed
POWERFULMOVES added a commit that referenced this pull request Jan 18, 2026
…oints-with-mocks

test(pmoves-yt): add endpoint tests
@POWERFULMOVES
POWERFULMOVES deleted the codex/add-tests-for-yt-endpoints-with-mocks branch March 7, 2026 21:41
POWERFULMOVES added a commit that referenced this pull request Apr 15, 2026
…ation (Rec #11-13) (#1234)

* feat(k8s): add NetworkPolicies for pmoves namespace isolation (Rec #11)

* test(common): add test coverage for shared library modules (Rec #12)

* refactor(agent-zero): migrate mcp_server from sync requests to async httpx (Rec #13)

* fix(agent-zero): keep execute_command sync, add async variant for new callers

* fix(k8s): correct network policy labels to match actual workload selectors

* fix(agent-zero): await execute_command_async directly in FastAPI caller (Codex P1 #1)

The _execute_command helper in main.py was dispatching via run_in_executor
to the sync wrapper (which internally spins up asyncio.run). This created
a wasteful async→thread→new-event-loop→async double-hop.

Now _execute_command awaits mcp_server.execute_command_async directly,
eliminating the thread pool indirection and preventing coroutine-object-
instead-of-dict response validation errors on /mcp/execute.

Refs: PR #1234 Codex P1 review finding on mcp_server.py:583

* fix(agent-zero): update async mcp test mocks

---------

Co-authored-by: PMOVES Bot <bot@powerfulmoves.ai>
POWERFULMOVES added a commit that referenced this pull request Apr 19, 2026
Advances gitlinks for 9 submodules whose PRs were merged this session:

Session PRs (z890-claude Phase 5.5):
- PMOVES-Archon: nested sub recovery + 36 integration commits (#12)
- PMOVES-BoTZ: Dockerfile CRLF + node user fix (#118)
- PMOVES-DoX: gitignore CGP artifacts + dependabot sync (#148)
- PMOVES-Tailscale: 4 PRs — docs, deploy scripts, Docker/Headscale, Cloudflare (#2-5)
- Pmoves-cipher: Ollama backend + MCP spec update (#3)

Also advanced (upstream movement during session):
- PMOVES-Agent-Zero, PMOVES-ClawZ, PMOVES.YT, Pmoves-Health-wger

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
POWERFULMOVES added a commit that referenced this pull request May 7, 2026
… handoff (#1418)

* chore(submodule-sync): gitignore kilo npm artifacts + AGNOTE4482 lane handoff

.gitignore:
- Add patterns for .kilo/ and .kilocode/ npm bootstrap artifacts
  (package.json, package-lock.json, node_modules/, bun.lock).
- Tracked agent/, command/, rules/, skills/ markdown files unaffected.
- Resolves untracked-noise that appears whenever the kilocode CLI
  bootstraps these dirs.

pmoves/docs/AGENTS/AGNOTE4482PHI.t1.md:
- Append signed CLAIM/REVIEW/RELEASE block for the
  CLAUDE-OPUS (Z890) submodule sync triage lane (2026-05-02).
- Document per-submodule findings: transcribe-and-fetch orphan
  gitlinks (upstream issue, deferred per CHIT-replacement plan),
  AgentGym lmrlgym advance (PR opened), Archon 4-pointer drift
  (Lane A handoff for CODEX-GPT5), BoTZ 7-pointer drift incl
  pmoves_cipher PR #1370 unblock (Lane B handoff for CODEX or
  sibling-CLAUDE), Lane C post-merge superproject gitlink sync.
- Both stale upstream promote branches (Archon
  chore/promote-nested-submodule-pointers PR #12 closed unmerged;
  BoTZ sync/archon-nested-skill-pointers no PR ever opened) marked
  do-not-extend; handoff agents open fresh branches.

Trail unsigned locally (no CHIT_PASSPHRASE in CLI session per repo
policy "Signing is optional locally").

Companion PR: POWERFULMOVES/PMOVES-AgentGym#1 (lmrlgym 83abeedb→911d726b).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(pr1418): apply CodeRabbit hedge-trim findings

Actionable (1):
- AGNOTE block: add `chit_artifact_path` field (required handoff
  field per PHI.t1.md lines 71-79). References plan file as the
  cross-agent handoff payload; documents unsigned-trail rationale
  inline per repo policy.

Nitpick #2 (Quick win):
- .gitignore: remove redundant `.kilo/node_modules/` and
  `.kilocode/node_modules/` lines — already covered by global
  `node_modules/` rule on line 80.

Nitpick #3 (Quick win):
- AGNOTE Lane A/B: prefix branch names with repo identifier
  (`chore/archon-promote-nested-pointers-2026-05` and
  `chore/botz-promote-nested-pointers-2026-05`) so handoff agents
  can disambiguate at a glance across multi-terminal contexts.

Nitpick #1 (Skipped — stylistic):
- .gitignore directory-level + negation pattern alternative —
  CodeRabbit notes "current explicit approach is also valid";
  explicit list keeps each artifact discoverable in code review.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(pr1418): apply final CodeRabbit nitpick — directory-level ignore + negation

Replaces explicit per-artifact list (package.json, package-lock.json,
bun.lock × 2 dirs) with directory-level ignore + negation pattern:

  .kilo/*                      → ignore all
  !.kilo/agent/                → keep tracked
  !.kilo/command/              → keep tracked
  .kilocode/*                  → ignore all
  !.kilocode/rules/            → keep tracked
  !.kilocode/skills/           → keep tracked

Future-proofs new artifact types (e.g. .yarnrc, dist/) without
needing additional .gitignore entries. Layout differs across the
two dirs (.kilo has agent/+command/; .kilocode has rules/+skills/)
so negations are dir-specific.

Verified:
- 24 tracked files still tracked (git ls-files unchanged)
- check-ignore confirms agent/, command/, rules/, skills/ contents
  are correctly NOT ignored
- bootstrap artifacts (package.json, package-lock.json, bun.lock,
  node_modules/) confirmed ignored

Closes nitpick #1 from CodeRabbit review on PR #1418.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Shaela Bello <slbello@uncg.edu>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
POWERFULMOVES added a commit that referenced this pull request Jun 9, 2026
…eam sync) (#1761)

Advances PMOVES-supabase a08627a4 -> 61116aee after fork PRs #11 (master sync,
supabase/supabase@master, 2233 commits) + #12 (hardened merge-forward, clean,
all pmoves_* hardening byte-preserved) merged through the now-protected hardened
branch. .gitmodules already tracks PMOVES.AI-Edition-Hardened (not trapped).

Image-built service -> post-merge Trivy is the CVE gate.
Handoff: research/SUPABASE_SYNC_HANDOFF_2026-06-09.md. Kong/CLI reconcile +
TAC_SUPABASE.md refresh tracked as follow-up (task #30).

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
POWERFULMOVES added a commit that referenced this pull request Jul 13, 2026
… pins, Cipher paths (#2106)

Deferred #9 (Docker socket): Noted as fleet-wide convention — same pattern
  as .claude/mcp.json and all 5090 scope configs. No change; needs cross-cutting PR.

Deferred #10 (Agent registry entry):
- Add full kilocode_glm entry to agent_registry.yaml (31 lines)
- class: specialized, primary_type: agent, secondary_type: llm
- port: 3030 (TensorZero gateway), health: /health
- layers: [L0, L3, L4, L5], evolution_stage: stage_2
- NATS publishes/subscribes, CHIT toggles, resilience, topology
- team: implementation, node_affinity: [5090, laptop-4090, z890]

Deferred #11 (ProviderCatalog registration):
- Add coding_kilocode function to chat_kilocode serves list in provider_catalog.yaml
- Add model_suit: kilo-auto-balanced reference
- Promote orchestrator weight 0.0 -> 0.3 (matches tensorzero.toml)

Deferred #12 (Cipher path consistency):
- Re-apply kilorules.md fix: replace dead /api/memory/search with MCP /sse endpoint
  (was reverted by merge)
- Fix .kilo/command/health.md: cipher-memory port 8096 -> 8105 (2 references)

Additional re-applies (reverted by merge):
- kilo.json: pin tailscale-mcp@2026.4.10-1 (was @latest)
- kilo.json: fix HuggingFace MCP @anthropic-ai/huggingface-mcp -> @llmindset/hf-mcp-server
- kilocode_glm.yaml: NATS url -> \ env var reference

Co-authored-by: Shaela Bello <slbello@uncg.edu>
POWERFULMOVES pushed a commit that referenced this pull request Jul 30, 2026
…lution

Addresses Codex P1 + Crush 4090 review on PR #2299.

Root cause: cipher-api was on pmoves_app, pmoves_data, pmoves_bus,
pmoves_external — but NOT pmoves_api. The per-agent token enforcement
(P #12) resolves cipher_<uuid> tokens via SUPABASE_REST_URL which
defaults to http://supabase-kong:8000/rest/v1. supabase-kong is on
pmoves_api + pmoves_public. Without pmoves_api, cipher cannot reach
supabase-kong → token lookups fail → authenticated requests rejected.

Fix: add pmoves_api to cipher-api networks in both canonical
(docker-compose.yml) and split overlay (docker-compose.agents.yml).
Also added to PMOVES_NETWORKS env var for consistency.
POWERFULMOVES added a commit that referenced this pull request Jul 30, 2026
… SSE fix) (#2299)

* chore(submodule): promote Pmoves-cipher → 2b4c4f7 (Phase B PR 2 + MCP SSE fix)

Advances cipher gitlink 986e6e2 → 2b4c4f7. Brings two fixes from
Pmoves-cipher PR #12 (squash-merged 2026-07-30):

1. Per-agent token enforcement (Phase B PR 2): auth.ts resolves
   cipher_<uuid> tokens via Supabase cipher_agent_tokens table;
   memory-routes.ts + mcp-sse.ts enforce resolved identity on every
   tool call (403 on mismatch, 400 when missing + token present,
   403 on wildcard in enforcement mode).

2. MCP SSE 400 fix: mount /mcp routes before express.json() so the
   MCP SDK's SSEServerTransport.handlePostMessage() gets the raw
   readable body stream (express.json() was consuming it first →
   "stream is not readable" → 400 → Crush pmoves-cipher MCP failed
   to initialize).

Verified live: POST /mcp/messages returns 202 Accepted after fix.

* fix(compose): attach cipher-api to pmoves_api for Supabase token resolution

Addresses Codex P1 + Crush 4090 review on PR #2299.

Root cause: cipher-api was on pmoves_app, pmoves_data, pmoves_bus,
pmoves_external — but NOT pmoves_api. The per-agent token enforcement
(P #12) resolves cipher_<uuid> tokens via SUPABASE_REST_URL which
defaults to http://supabase-kong:8000/rest/v1. supabase-kong is on
pmoves_api + pmoves_public. Without pmoves_api, cipher cannot reach
supabase-kong → token lookups fail → authenticated requests rejected.

Fix: add pmoves_api to cipher-api networks in both canonical
(docker-compose.yml) and split overlay (docker-compose.agents.yml).
Also added to PMOVES_NETWORKS env var for consistency.

---------

Co-authored-by: Agent Zero <agent.zero@pmoves.ai>
POWERFULMOVES added a commit that referenced this pull request Aug 15, 2026
… Actions workflows + 5 NATS subjects (#2568)

* feat(nats+workflows+publisher): Mavis harness v0 follow-ups — drift publisher + drift cron + ruleset auto-enroll

Closes the 3 Mavis harness v0 follow-up items from the PR #2477 lock
(NATS subject registration, Mavis cron, auto-apply on fork creation).
The follow-ups are now in a form that runs end-to-end via GitHub
Actions; the orchestrator + the drift publisher share the same
`Publisher` Protocol, so the test surface is uniform.

What lands:

  - pmoves/tools/branch_protection_publisher.py (245 lines) — the drift
    publisher. Wraps branch_protection.drift_check() output and
    publishes one message per non-compliant repo to
    pmoves.branch_protection.drift.v1. Follows the same `Publisher`
    Protocol as orchestrator.Publisher so the test surface is
    uniform. Three sinks: MockPublisher (in-memory, for tests),
    FilePublisher (JSONL to file or stdout, the default sink for
    GitHub Actions), NatsPublisher (lazy-imported; the real
    pmoves-nats-mcp wire-up when it's live). The `envelope` shape
    wraps each payload in {envelope, source, published_at, audit} so
    subscribers can filter by source + order by time. Compliant
    repos are silent (publishing every audit would flood the subject).
    CLI: `python -m pmoves.tools.branch_protection_publisher
    --org POWERFULMOVES --spec <spec> --sink <file|nats> [--out <path>]`.

  - .github/workflows/branch-protection-drift.yml — daily 06:00 UTC
    drift cron (matches fork-sync.yml + branch-protection-sync.yml).
    Runs the publisher, surfaces the summary to the step summary,
    uploads the per-run JSONL artifact. workflow_dispatch for manual
    runs with --sink file|nats choice. nats sink defers to the
    pmoves-nats-mcp slice.

  - .github/workflows/branch-protection-ruleset-sync.yml — the
    ruleset-side auto-enroller. Two trigger modes: workflow_dispatch
    (operator dispatches with --repo or every per_repo_overrides
    entry; --no-dry-run flips dry-run off) + weekly Sun 04:00 UTC
    safety net. Mints the GitHub App token with
    permission-administration: write (the live validation of the App's
    grant; same pattern as branch-protection-sync.yml). Org-level
    repository.created is the natural auto-enroll hook for new forks;
    lands in a follow-up slice when the org App gets
    repository.created wired.

  - .claude/context/nats-subjects.md — 5 subjects registered (Mavis
    Harness v0 Subjects section): pmoves.agent.task.v1 (orchestrator
    dispatch), pmoves.agent.result.v1 (worker reply), pmoves.bpm.phase.v1
    (BPM phase transition), pmoves.bpm.pomodoro.v1 (focus-block
    boundary), pmoves.branch_protection.drift.v1 (per-repo drift
    envelope). Each entry includes Direction, Purpose, Payload
    schema, Subscribers, Status (REGISTERED).

Why GitHub Actions and not a mavis cron: the mavis CLI on this node
has an installer path bug (resources\resources\daemon\cli.js —
duplicated resources\) so the mavis cron path is blocked until
that's fixed. The GitHub Actions path is the cross-host alternative
and matches the existing PMOVES pattern (fork-sync.yml uses
schedule: cron too). The publisher's function signature is
identical, so when the mavis runtime is on the same host as NATS,
swap the workflow for a mavis cron that calls the same
`publish_drift_for_org(org, NatsPublisher())` — no other code
changes.

Three-body: delivery=Mavis, control=DARKXSIDE, memory=this commit +
the publisher + the workflows + the NATS subjects registration +
the test surface (in the next commit). CHIT trail unsigned-local.

* test(tools): 17 tests for branch_protection_publisher across 5 groups

* docs(baseline+learnings+agnote): Mavis harness v0 follow-ups wire-up + 3 new pair-review lessons

Closes the docs + LEARNINGS + AGNOTE trail for the Mavis harness v0
follow-ups slice.

What lands:

  - pmoves/docs/operations/BRANCH_PROTECTION_BASELINE.md — wire-up
    section now lists the 2 new workflows + the publisher module +
    a 5-row NATS subjects table (producer / consumer / purpose).
    The "Known gap" note about the Slice 2 wrong-branch rulesets is
    removed (the post-merge re-applies on 2026-08-15 fixed both
    forks; the 4090's lesson #8 captured the structural cause + fix).

  - pmoves/tools/LEARNINGS/branch-protection-v0_LEARNINGS.md — 3 new
    pair-review lessons (#10-#12) for the drift publisher pattern,
    the GitHub Actions vs mavis cron decision, and the auto-apply
    org-level repository.created App event. The 5-class taxonomy
    gains 5 new pre-existing rows (the 5 NATS subjects, the
    publisher, the drift cron, the auto-enroll workflow, the
    Slice 2 wrong-branch fix). Lesson count is now 12 (was 9 in
    the 4090's last update).

  - pmoves/docs/AGENTS/AGNOTE4482PHI.t1.md — new
    Mavis::MAVIS-HARNESS-V0-FOLLOWUPS::2026-08-15 trail row,
    recording the publisher + 2 workflows + 5 NATS subjects
    registration + the test surface + the GitHub Actions vs
    mavis cron rationale (the mavis CLI installer path bug
    blocks the runtime path; cross-host NATS publish needs the
    workflow pattern). CHIT trail unsigned-local.

Why GitHub Actions vs mavis cron (captured in the AGNOTE row +
LEARNINGS #11): the mavis runtime on the operator's node has an
installer path bug (duplicated `resources\`) so the cron side is
blocked until that's fixed. The GitHub Actions path is the
cross-host alternative and matches the existing PMOVES pattern
(fork-sync.yml uses schedule: cron too). The publisher's
function signature is identical, so the swap is one-line when
the runtime is on the same host as NATS.

Three-body: delivery=Mavis, control=DARKXSIDE, memory=this commit +
the AGNOTE row. CHIT trail unsigned-local.
POWERFULMOVES added a commit that referenced this pull request Aug 15, 2026
…sync + 14 regression tests (#2569)

* fix(workflows): 4 P1 codex bugs in branch-protection drift + ruleset-sync

Post-merge fix for the 4 P1 bugs codex caught on PR #2568. The
unit tests passed and CI was green, but the workflow glue between
the Python tool and the GitHub Actions runtime was broken in 4
ways no unit test could catch. Lesson #12 in the LEARNINGS file
captures the structural lesson.

The 4 fixes:

  1. branch-protection-ruleset-sync.yml apply loop passed the
     repo POSITIONALLY and added a NONEXISTENT --dry-run flag.
     Arg-parse failed silently; every scheduled run was a no-op.
     Fix: args=("--repo" "$repo") + conditional --no-dry-run (only
     when DRY_RUN=false; dry-run is the default).

  2. branch-protection-drift.yml had no GitHub App token mint.
     drift_check() 401s on every repo and reports each as
     audit_error — the daily cron was publishing FALSE drift.
     Fix: add actions/create-github-app-token step with
     owner: github.repository_owner + permission-contents: read
     (no admin needed for read-only drift), pass GH_TOKEN to
     the drift step.

  3. The drift pipeline used set -e without pipefail. tee step's
     0 exit masked publisher failures. Fix: set -e -o pipefail.

  4. The resolve step wrote repos.tsv but never defined a repos
     step output. Empty repositories + owner set escalates to
     EVERY accessible repo. Fix: repos<<__EOF__ ... __EOF__ heredoc
     to $GITHUB_OUTPUT, so the App-token step gets the resolved
     set.

Plus one I caught myself (not in the codex review, same pattern):
the ruleset-sync apply loop's if python | tee; then APPLIED else
FAILED branch also relied on tee's 0 exit, so FAILED was never
incremented. Fix: capture python exit via ${PIPESTATUS[0]}.

Both workflows now match the patterns in the existing
branch-protection-sync.yml (which has them right — I just didn't
copy them over).

Three-body: delivery=Mavis, control=DARKXSIDE, memory=this commit
+ the AGNOTE row. CHIT trail unsigned-local.

* test(workflows): 14 regression tests for the 4 P1 codex bugs

* docs(learnings+agnote): workflow glue lesson #12 + 5-class taxonomy update

Captures the structural lesson from the 4 P1 bugs codex caught
post-merge on PR #2568. The 17/17 unit tests passed, CI was green,
and the operator admin-merged — and the workflows were broken in
ways no unit test could catch.

What lands:

  - pmoves/tools/LEARNINGS/branch-protection-v0_LEARNINGS.md:
    + Lesson #12 "Workflow glue needs code review, not just unit
      tests" (200+ words). The 4 P1s listed, the pattern that
      missed them (unit tests cover the Python, not the
      workflow-tool glue), and the corollary to lesson #9 (the
      17/17 was a "test that could only assert the Python was
      correct", not that the workflow invoked it correctly).
    + Lesson count 9 -> 12.
    + 5-class taxonomy: 1 new already-fixed row (the 4 P1 silent
      patterns) + 16/6/5/9 totals (was 15/6/5/9).
    + Title updated: "5-class taxonomy + 12 pair-review lessons".

  - pmoves/docs/AGENTS/AGNOTE4482PHI.t1.md: new
    Mavis::BRANCH-PROTECTION-WORKFLOWS-FIX::2026-08-15 trail row.
    Documents the 4 P1s + the 1 self-caught pipefail-style bug in
    the apply loop, the fix shape, and the structural lesson
    captured. CHIT trail unsigned-local.

Three-body: delivery=Mavis, control=DARKXSIDE, memory=this commit
+ the LEARNINGS file + the AGNOTE row + the 14 regression tests
in the previous commit. CHIT trail unsigned-local.
POWERFULMOVES pushed a commit that referenced this pull request Aug 19, 2026
The MiniMax-Provider-Verifier is the conformance gate for adding a
new provider to the Mavis model cascade. It REQUIRES real API calls
(--api-key, --base-url, --model) so it cannot run in CI without
exposing secrets.

This workflow runs the static half
(pmoves/tools/provider_verifier_gate.py) on every PR that touches
the relevant paths. 6 checks, no API calls, JSON output consumed
by the step summary + a PR comment on FAIL.

The status check name is verifier-gate; merge-gate.yml references
it (so a FAIL blocks merge). workflow_dispatch is the operator's
full-conformance path: the gate runs the static checks + the
operator runs verify.py locally with the API keys.

Workflow structure (lesson #12/13 from the post-merge fix — the
test file lives separately so a future edit doesn't silently break
the gate):

  triggers:
    - pull_request to main on Pmoves-MiniMax-Provider-Verifier/**,
      pmoves/tools/provider_verifier_gate.py, the test file, and
      the workflow file itself
    - workflow_dispatch for manual full-conformance runs

  permissions: {}   # minimal; no scopes the job doesn't need

  concurrency:      # one concurrent run per ref; cancel on
                    # re-runs of the same PR (matches existing
                    # branch-protection-drift.yml pattern)

  steps:
    1. actions/checkout@<SHA> with submodules: recursive
       (so Pmoves-MiniMax-Provider-Verifier/ is on disk)
    2. actions/setup-python@<SHA> Python 3.12 (matches the
       verifier's pyproject.toml requires-python)
    3. Best-effort install of the verifier's runtime deps
       (numpy, openai, megfile). The static gate doesn't need
       them; the full dispatch path does.
    4. Run the static gate, capture the JSON output, write to
       GITHUB_STEP_SUMMARY, exit 0/1 on PASS/FAIL.
    5. On FAIL, post a PR comment with the common-cause list
       (per the comments pattern in codeql.yml / claude.yml).

The shell pipeline uses \set +e ... ; RC=\True ... set -e\ because
the gate's \py ... --json\ exits 1 on FAIL and we need to
capture BOTH the output and the exit code. This is the same
pattern as the post-merge fix in PR #2569 (lesson #13: \set -e\
without \pipefail\ masks publisher failures).

CHIT trail unsigned-local.
POWERFULMOVES pushed a commit that referenced this pull request Aug 19, 2026
…global

The 6 check functions used module-level globals (VERIFIER_SUBMODULE,
PROVIDER_CONFIG, etc.) resolved at import time. The first test
pass wrote the helper to work against the live submodule; the
test run then mutated gate.VERIFIER_SUBMODULE between tests, but
the check functions kept reading the cached paths from the
global — so tests for missing-syntax, missing-config, etc. read
the LIVE files, not the fixture files. 15/22 tests failed for
that reason.

Refactor: each check function now takes verifier_submodule: Path
as an explicit parameter. run_gate() resolves the path (default
to the live submodule) and passes it through. Tests pass a
tmp_path fixture; the CLI takes --verifier-submodule as a flag.
The helper's behavior is unchanged on the live submodule.

Also adds 15 workflow-glue tests in
pmoves/tests/test_provider_verifier_workflow.py (lesson #12/13
from PR #2569 — workflow YAML needs code review, not just unit
tests; these tests parse the YAML and assert on the patterns
that bind the workflow to the helper's contract: trigger block,
permissions, concurrency, job name, pinned checkout, submodules:
recursive, --json flag, exit code capture + propagation, py
alias). 15/15 pass.

CHIT trail unsigned-local.
POWERFULMOVES pushed a commit that referenced this pull request Aug 19, 2026
… patterns from this slice

5 patterns captured in §2:

  2.1 Two-piece gate — why not a single CI workflow that runs
      verify.py. The verifier needs real API calls; the F-07
      supply-chain note forbids exposing the keys in a workflow
      file. The gate is the static half (PR-time, no API calls,
      catches the cheap failures) + the full half (operator's
      manual step on the local node, where env.shared holds the
      keys).

  2.2 Workflow permissions: workflow-level {} + job-level
      issues: write. The PR-comment step needs issues: write;
      the static check needs no scopes. Job-level override is
      the minimum scope that lets the comment step post.

  2.3 The skipped == failure regression — paths-filter
      workflows don't always run. A naive merge-decision that
      treats skipped as failure blocks every non-touched PR.
      The test asserts the absence of the naive check so a
      future 'fix' doesn't regress.

  2.4 Test the binding, not just the parts (lesson #12/13 from
      PR #2569). 15 workflow-glue tests assert the patterns
      that bind the workflow to the helper's contract: trigger
      block, paths filter, permissions, concurrency, job name,
      pinned checkout, --json flag, RC=\True exit code capture
      + propagation, py alias.

  2.5 Helper refactor: parameter, not module global. The first
      version used module-level globals resolved at import
      time; tests that mutated the global kept reading the
      cached paths, so 15/22 tests failed for that reason. The
      fix: each check function takes verifier_submodule: Path
      as a parameter. The helper's behavior on the live
      submodule is unchanged.

Cross-refs in §4 point at every file this slice touched, plus
the related PRs (2586, 2589, 2590, 2568, 2569, 2612) and the
operator (three-body delivery=Mavis, control=DARKXSIDE,
memory=this trail).

CHIT trail unsigned-local.
POWERFULMOVES added a commit that referenced this pull request Aug 19, 2026
…CI (#2623)

* feat(agent-registry): register PMOVES-MiniMax-MCP as the MiniMax model surface

The MCP server is the model-side counterpart of the Mavis harness v0:
WebUI/Agent-Zero call it directly via stdio; the sidecar lane (Pinokio
on operator devices) calls the same surface via Pmoves-minimax-cli.

Three surfaces updated to match the existing mcp_servers convention:

  - pmoves/config/agent_registry.yaml: pmoves_minimax_mcp entry under
    mcp_servers (status: active, transport: stdio, command: uvx). The
    harness v0 follow-ups registered the SKILL surface in
    pmoves/configs/submodule_skill_registry.json:770; this entry is the
    discovery-plane twin that lets rooms and rooms-tools find the server
    by name.

  - .claude/mcp.json: pmoves-minimax-mcp entry with command/args/env
    matching the submodule's pyproject.toml entry point
    (minimax_mcp.server:main). Env vars follow the demo config: API key
    + host + resource_mode. Pinned by reference, not by version
    (uvx resolves on demand).

  - .claude/BOOTSTRAP.md: new row in the MCP Entrypoints table so a
    cold-start agent sees the MiniMax model surface alongside cipher,
    docker, hostinger, tailscale.

CHIT trail unsigned-local; AGNOTE entry to follow at slice close.

* feat(cgp): add pmoves-minimax-mcp + minimax service block to bootstrap profile

The 3 NEW model-cascade submodules (PMOVES-MiniMax-MCP, Pmoves-minimax-cli,
Pmoves-MiniMax-Provider-Verifier) landed via PR #2589 of the 6-repo fold-in
but the bootstrap CGP didn't reflect them. Consumer forks
(PMOVES-hermes-agent, PMOVES-pinokio) read this profile at init and use the
mcps + services blocks to wire their own tool surfaces.

Two changes to example.cgp.yaml:

  - mcps: add pmoves-minimax-mcp so a consumer can stand up the model
    surface as one of its MCP servers without re-discovering the registry.

  - services: add a minimax: block that names the 3 submodules by their
    canonical paths and binds the sidecar lane to Pmoves-pinokio. This
    gives the operator a single place to point at when wiring the model
    cascade - the entry that was missing between the registry row and
    the runtime wire-up.

The harness v0 follow-ups (PR #2568) consumed the new submodules from
their hardcoded list; this commit makes the CGP profile a true source
of truth, so when a future fork (e.g. a KiloCode sidecar or a
NOT-YET-STOOD-UP Hermes-via-Pinokio bridge) loads the bootstrap it
inherits the minimax surface for free.

CHIT trail unsigned-local; AGNOTE entry to follow at slice close.

* docs(verifier): add PROVIDER_VERIFIER_GATE.md — how to run the conformance gate

The Pmoves-MiniMax-Provider-Verifier submodule is registered in the
submodule_skill_registry (PR #2589 of the 6-repo fold-in) but had no
PMOVES-side doc telling an operator how to use it. The submodule's
own README documents the verifier's internals; this doc is the Mavis
side: thresholds, invocation, the three failure modes the gate is
the only thing that catches (top-k drift, key re-sorting, error-only
reasoning), and the CI follow-up that's deliberately out of scope
for the wire-up PR.

Audience: an operator adding a new MiniMax-compatible provider
(Want to add openrouter-minimax? Follow the recipe in §1-3), a
reviewer deciding whether to accept a new provider entry, and a
future CI maintainer wiring the gate into python-tests.yml.

The doc also captures the 'what this is NOT' section: not a load
test, not a security test, not a model-quality test. The verifier
checks *correctness vs the official baseline*, not throughput or
adversarial robustness. That's the load-bearing distinction for any
future PR that proposes 'just add a smoke test' as a substitute.

CHIT trail unsigned-local; AGNOTE entry to follow at slice close.

* docs(cli): add MMX_CLI_SURFACE.md — catalog the mmx-cli surface

The Pmoves-minimax-cli submodule (npm: mmx-cli, v1.0.19) is the sidecar
lane's entry point: Pinokio apps on operator devices, headless scripts,
CI jobs. The submodule's own AGENTS.md/SDK.md/ERRORS.md are
developer-facing (build rules, programmatic API, full error catalog);
this doc is the Mavis-side summary for an operator who needs to
know what the CLI can do and when to use it instead of the MCP.

Three sections do the load-bearing work:

  - Command tree (auth, config, file, image, music, quota, search,
    speech, text, video, vision, help, update) with a one-line
    purpose + invocation example per command.

  - SDK surface showing the programmatic entry point
    (import { MiniMaxSDK } from 'mmx-cli/sdk') for the four core
    modalities: text, image, video, with the H3 V2 model and the
    async taskId path for video.

  - The five errors you'll hit most often, distilled from the 70+
    error scenarios in ERRORS.md. The doc points to ERRORS.md for
    the full taxonomy and notes that scripts should match on the
    error code, not the message.

The closing section 'Why not always call the MCP?' captures the
splittest that the sidecar lane has to make: MCP for tool-aware
agents, CLI for everything else. The MCP and CLI cover the same
model surface; the split is *who's calling*, not *what's being
called*.

CHIT trail unsigned-local; AGNOTE entry to follow at slice close.

* docs(skills): add PMOVES_SKILLS_REVIEW.md — review the find-skills meta-skill

The skills/PMOVES-skills/ submodule landed via PR #2586 of the 6-repo
fold-in as the post-recenter home of the open-skills-ecosystem library
(was MiniMax-AI/skills, now vercel-labs/skills). The submodule
ships one bundled skill: find-skills, a meta-skill that helps an
agent decide 'is there a skill for X?' via the npx skills CLI.

This doc is the Mavis-side review: what the submodule brings, what's
worth pulling into our catalog, what's worth leaving alone.

Three findings:

  1. The find-skills skill itself should land in
     pmoves/configs/skill-pairings.yaml so BoTZ pipelines can
     surface it to agents on the sidecar lane. One-line addition;
     documented as a proposed delta in the doc body.

  2. The skills CLI should land in pmoves/configs/cli_tools.yaml
     host_clis so the doctor (make -C pmoves cli-check) validates
     it alongside the other host CLIs. Also a one-line addition.

  3. The PMOVES-specific default --owner POWERFULMOVES should be
     encoded in the PMOVES-side wrapper, not in the upstream
     SKILL.md (we don't own it; editing it would create fork drift).
     Documented here as a behavioral default.

The 'leave alone' section makes the recenter discipline explicit: no
upstream edits, no PMOVES-MiniMax-skills as a separate submodule,
no submodule-level patches. If we need fork-specific behavior, it
goes in pmoves/tools/, not in the submodule.

CHIT trail unsigned-local; AGNOTE entry to follow at slice close.

* docs(agents-md): add AGENTS_MD_FORMAT_REVIEW.md — review the open format vs our AGENTS.md

The PMOVES-agents.md/ submodule landed via PR #2590 of the 6-repo fold-in
as the canonical home of the agentsmd/agents.md open format. The submodule
is a Next.js docs site (not a library) at https://agents.md/.

This doc is the Mavis-side review: what the open format is, how our
PMOVES.AI AGENTS.md compares, and what to adopt.

The open format is deliberately minimal — three canonical sections
('Dev environment tips', 'Testing instructions', 'PR instructions').
Our PMOVES.AI AGENTS.md has 12 sections; 4 map to the open format
and 8 are PMOVES extensions (Project Structure, Known Roads,
env.shared, Compose layering, Coding Style, Secrets, Submodule
Workflow, Deployment, Security, AGENTS.md Format Reference).

Three adoption items, in priority order:

  1. Rename 'Build & Development Commands' → 'Dev environment tips'
     to match the open format. A one-line content edit; high-signal
     conformance win.

  2. Promote the 'AGENTS.md Format Reference' section to a top-of-file
     note that points at PMOVES-agents.md/ as the canonical spec home.
     Also a small edit; better cold-start ergonomics for any agent
     that knows the open format.

  3. Document the 'PMOVES extensions' convention. The 8 PMOVES-specific
     sections don't have a name in the open format. Pick a marker
     (e.g. '### PMOVES extension: <name>') and apply it incrementally.

All three adoption items are deliberately out of scope for this PR —
they're content edits to AGENTS.md, not wire-up work. This doc
documents them so the operator can land them in a follow-up.

CHIT trail unsigned-local; AGNOTE entry to follow at slice close.

* docs(cipher): store cipher skill context — what's encrypted, where, CHIT custody

Cipher is the PMOVES memory layer; the Mavis-class agent re-reads
prior plans on cold start and writes on every BPM phase boundary.
The operator's request to 'store cipher context' was about capturing
the load-bearing facts in one place, not about adding new code.

This doc covers four sections that the rest of PMOVES already
references but doesn't pull together:

  - What cipher is for: persistent agent memory + reasoning trace
    storage + CHIT custody chain. Three distinct things, one MCP
    server.

  - What's encrypted: the memory body (HMAC-SHA256 + AES-256-GCM)
    via CHIT passphrase. The memory index (titles, timestamps,
    category) is plaintext so the MCP server can answer searches
    without decrypting every record.

  - Where the keys live: \ per-node in env.shared,
    the host-side and container-side names are the same after the
    sync-secrets-local.yml fix in PR #2605. HMAC kid public, HMAC
    secret derived at signing time, never written to disk.

  - NATS custody chain: cipher publishes three subjects
    (cipher.memory.stored.v1, cipher.memory.searched.v1,
    cipher.reasoning.stored.v1) to monitoring. The CHIT signing
    flow is sign-trail → cipher write → chit.signed.v1 publish,
    and the HMAC signature is the trust anchor for the AGNOTE
    append-only chain.

The closing 'How a Mavis-class agent should use it' section codifies
the three patterns (read on cold start, write on phase boundaries,
sign before state-changing actions) so a future Mavis session has
the recipe in one place.

The 'What cipher is NOT' section captures the boundary: not a search
engine, not a database, not a KV store, not a real-time channel.
Each of these has its own PMOVES subsystem; cipher is the memory.

CHIT trail unsigned-local; AGNOTE entry to follow at slice close.

* docs(hirag): store HiRAG context — submodule state, what's wired, what's pending

HiRAG is PMOVES's hybrid retrieval layer (vector + graph + full-text).
The operator's request to 'store HiRAG context' was about capturing
the surface in one place, not adding new code. The agent registry
already declares the MCP server (pmoves_hirag_mcp, status: 'planned',
grounding_source: true) but the doc that explains what that entry
means, and what an agent should do with it, didn't exist.

This doc covers the four sections the rest of PMOVES already
references but doesn't pull together:

  - What HiRAG is: three-way retrieval (Qdrant vector + Neo4j
    graph + MeiliSearch full-text) merged at the gateway. The
    legacy and v2 gateways are the two HTTP surfaces; the MCP
    server is the discovery-friendly entry point.

  - What's wired: registry entry, compose services (CPU + GPU
    variants on the legacy profile), smoke test path. Three
    concrete facts a future Mavis session can use without
    re-discovering the surface.

  - What's pending: .claude/mcp.json registration (the registry
    has the entry; mcp.json doesn't have the server), v2 gateway
    promotion (v2 is documented as 'preferred' but legacy is
    what's deployed), submodule freshness check (the algorithm
    substrate tracks PMOVES.AI-Edition-Hardened, not main, and
    a drift could land silently).

  - How a Mavis-class agent should use it: read on cold start
    for grounding (the registry's grounding_source: true flag
    is a recipe, not a label), issue hybrid queries for
    cross-cutting context, fall back to scoped search for
    narrow questions.

The 'What HiRAG is NOT' section captures the boundary: not a
primary database, not a real-time index, not a replacement for
cipher, not a search engine. Each of these has its own PMOVES
subsystem; HiRAG is the hybrid read.

CHIT trail unsigned-local; AGNOTE entry to follow at slice close.

* feat(registry): wire find-skills meta-skill into cli_tools + skill-pairings

The skills/PMOVES-skills/ submodule (vercel-labs/skills recenter, PR
#2586) ships a find-skills meta-skill that helps an agent decide
'is there a skill for X?' via the npx skills CLI. The CRUSH lane
(PR #2599) already wired the mmx-cli and the pmoves Wrappers; this
commit lands the two remaining entries that the PMOVES_SKILLS_REVIEW.md
review flagged as 'proposed deltas' for the find-skills meta-skill.

Two entries in two manifests:

  - pmoves/configs/cli_tools.yaml: new host_clis entry skills:
    backing the npx skills CLI. The doctor (make -C pmoves
    cli-check) now validates the npx skills --version invocation
    alongside gh, docker, make, uv, glances. required: false
    (find-skills is a convenience, not a hard requirement).

  - pmoves/configs/skill-pairings.yaml: two new skill_sources
    entries - cli-host-skills (matching the cli-host-* naming
    pattern) and ind-skills (the path to the SKILL.md). The
    second makes the meta-skill discoverable to BoTZ pipelines
    that consume the skill_sources block.

The PMOVES-side behavioral default '--owner POWERFULMOVES first,
then unfiltered' stays in PMOVES_SKILLS_REVIEW.md, NOT in the
upstream SKILL.md - editing the upstream would create fork drift.

CHIT trail unsigned-local; AGNOTE entry to follow at slice close.

* test(wireup): add 13 drift-detector tests for the model-cascade wire-up

The mcpcli-wireup slice added entries to 5 surfaces:
  - pmoves/config/agent_registry.yaml (mcp_servers.pmoves_minimax_mcp)
  - .claude/mcp.json (mcpServers.pmoves-minimax-mcp)
  - .claude/BOOTSTRAP.md (MCP Entrypoints table)
  - pmoves/contracts/schemas/pmoves-bootstrap/example.cgp.yaml
  - pmoves/configs/cli_tools.yaml (host_clis.skills)
  - pmoves/configs/skill-pairings.yaml (find-skills + cli-host-skills)

If any of these entries is removed in a future PR, the harness v0
loses the model surface silently. This test suite parses the actual
files (no mocks, no fixtures that would mask drift) and asserts the
load-bearing facts of each wire-up entry are present.

13 tests in 5 groups:
  - agent_registry (3): key, submodule pointer, capabilities
  - mcp.json (2): key, command + args
  - bootstrap_md (1): substring presence
  - example_cgp (2): mcps list, services.minimax block with mcp/cli/verifier
  - cli_tools (2): host_clis.skills, check command
  - skill_pairings (3): find-skills key + path, cli-host-skills key

The test names name the wire-up explicitly (test_*_minimax_mcp,
test_*_find_skills, etc.) so a future rewire has to be intentional:
if you rename 'pmoves_minimax_mcp' to 'pmoves_model_mcp', the test
fails and the commit message has to address why.

CHIT trail unsigned-local; AGNOTE entry to follow at slice close.

* agnote: CLAIM+RELEASE the mcpcli-wireup slice (Mavis::MCPCLI-WIREUP-CLAIM-RELEASE)

The mcpcli-wireup slice is closed. 11 commits land across 5 surfaces:
agent_registry, .claude/mcp.json, BOOTSTRAP.md, the CGP bootstrap
profile, and the docs/ + context/ trees. 4 review docs, 4 context
docs, 1 test file (13 tests, all green), 1 AGNOTE entry. The 3 NEW
model-cascade submodules (PMOVES-MiniMax-MCP, Pmoves-minimax-cli,
Pmoves-MiniMax-Provider-Verifier) from PR #2589 of the 6-repo
fold-in are now wired into the harness v0 end-to-end.

CLAIM+RELEASE row appended per the append-only discipline. The
GRAGHITI_MARK trailer (Mavis::MCPCLI-WIREUP-CLAIM-RELEASE::2026-08-18)
is the next agent's trust anchor.

CHIT trail unsigned-local. AGNOTE-style trail; sign-trail is the
operator-step (B850 has the trail-signed lane). LEARNINGS file
follows in the next commit.

* learnings: add mcpcli_wireup_LEARNINGS.md — capture the 5 patterns from this slice

5 patterns captured in §2:

  2.1 Why docs, not a new CLI — the operator's 'no need to hand roll'
      rule. When the tool is already installed and the operator said
      'don't hand-roll', the PMOVES-side work is wire-up + docs,
      not new code. New code is the trap.

  2.2 The 5-surface wire-up pattern — when an MCP server enters
      the harness, it gets registered in 5 places: agent_registry
      mcp_servers, .claude/mcp.json, BOOTSTRAP.md, the CGP mcps
      list, and the CGP services block. Skipping any one leaves
      a gap that will bite later.

  2.3 The transport field is not 'always SSE' — the stdio entries
      use command + args + env instead of endpoint. The schema
      accepts both shapes via additionalProperties: true.

  2.4 'Grounding source' is a recipe, not a label — the flag tells
      the discovery plane to fetch startup grounding from the
      server on cold start. Set it for memory/retrieval/model
      surfaces; skip it for tools used during a task.

  2.5 The drift detector pattern — wire-up work is best tested by
      parsing the files and asserting presence/shape, not by
      running the resulting system. The unit test names name the
      wire-up so a future rename is explicit.

Cross-refs in §4 point at every file this slice touched, plus
the related PRs (2586, 2589, 2590, 2568, 2599) and the operator
(three-body delivery=Mavis, control=DARKXSIDE, memory=this trail).

CHIT trail unsigned-local.

* fix(cgp): add minimax to v1.schema.json services whitelist

The CI caught what local YAML validation missed: the pmoves-bootstrap v1
schema enforces additionalProperties:false on the top-level services
object (to catch typo'd service names like 'tnailscale'). My example.cgp.yaml
adds a minimax: block to services.minimax, which the schema rejected
with 'Additional properties are not allowed (minimax was unexpected)'.

Failing tests (pmoves/tools/tests/test_load_bootstrap.py + test_orchestrator.py):
20+ tests, all cascading from the same root cause.

Fix: add minimax to schema.services.properties with the same
additionalProperties:true / typed-string properties pattern as
tailscale / rustdesk / hostinger / cloudflare. The block names the 4
submodule paths (mcp, cli, verifier, pinokio) so a future typo in
the path string is caught at the schema level rather than silently
passing through.

All 36 tests in test_load_bootstrap.py + test_orchestrator.py pass
after the fix; 13/13 drift-detector tests in test_mcpcli_wireup.py
also still pass.

CHIT trail unsigned-local; this is a fix-up commit in the 3-stacked
group.

* fix(mcp): pin minimax-mcp to the audited submodule version

`uvx minimax-mcp` with no version resolves the latest PyPI release at every
cold start, in the one file whose line 2 declares:

  "Dependencies pinned to exact versions on 2026-05-14 to prevent supply chain
   attacks (F-07). Re-pin after auditing new releases before upgrading."

Every other server in the file follows that (hostinger-api-mcp@0.2.1,
tailscale-mcp@2026.4.10-1, postgres-mcp@0.3.0, ...). This entry did not, while
its own _purpose names an audited submodule commit it never actually invoked.

The pin is unambiguous rather than a guess: the gitlink on this branch is
f4d6a61b, and that commit's pyproject.toml declares version 0.0.18 -- the same
as the current PyPI latest. So pinning changes nothing today and prevents the
next release from arriving unreviewed. minimax-mcp is a 0.0.x package (18
releases), so "latest" carries no API-stability promise either.

Also extended the file's own inventory note so the pin is recorded where the
policy is stated, not only at the call site.

Out of scope, reported not fixed: `huggingface` -> `@llmindset/hf-mcp-server`
is also unpinned. Pre-existing, unrelated to this lane.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* test(mcpcli): assert the minimax pin, not just the package name

The pin in the previous commit broke this PR's own test, which asserted
`"minimax-mcp" in args` — true only for the bare, unpinned form.

Tightened rather than relaxed: it now requires a `minimax-mcp==<version>`
entry, so the exact-version policy declared on line 2 of .claude/mcp.json is
enforced by a test instead of by convention. A future edit that drops back to
the bare package name fails here.

13 passed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(rooms): point pmoves_minimax_mcp at real rooms (not a fabricated one)

The pmoves_minimax_mcp entry I added in commit e6621fe included
rooms: ['minimax-mcp.room.model', 'hermes-agent.room.control']. The
first one is a made-up name — no such room exists in
pmoves/config/rooms/. The 4090 hit this when trying to bind: 'binding
leads nowhere' = the catalog has no minimax-mcp.room.model to bind
to.

Existing MCPs reference the rooms where they're actually consumed:
  - cipher → z890-infra.room.fabric
  - nats    → z890-infra.room.fabric, hermes-agent.room.control
  - voice  → 5090-voice.room.studio
  - e2b    → 4090-field.room.control, hermes-agent.room.control

The minimax MCP is consumed by:
  - z890-infra.room.fabric  — Mavis's runtime home (matches cipher/nats)
  - hermes-agent.room.control — Hermes consumer (matches e2b/hirag)
  - darkxsides.room — the DARKXSIDE persona, which calls Mavis

Replaced 'minimax-mcp.room.model' with the real 3-room list. No
schema change needed (rooms is a free-form string array). 13/13
drift-detector tests + 24/24 bootstrap tests still pass.

CHIT trail unsigned-local; this is a fix-up commit in the 3-stacked
group for #2612, blocking 4090's admin-merge.

* feat(gate): add pmoves/tools/provider_verifier_gate.py — the static CI gate

The Pmoves-MiniMax-Provider-Verifier submodule is the conformance
gate for adding a new provider to the Mavis model cascade, but it
REQUIRES real API calls (--api-key, --base-url, --model). A literal
'run verify.py on every PR' gate would need the verifier's secrets in
GitHub Actions, which the F-07 supply-chain note explicitly forbids.

This module is the static half of the gate. 6 checks, no API calls:

  1. verifier_submodule_present — the submodule is initialized
  2. provider_config_well_formed — provider.json.example parses as a
     JSON array
  3. provider_entries_have_required_fields — every entry has the 4
     required fields (name, model, base_url, api_key)
  4. example_keys_are_placeholders — no real API key accidentally
     committed to the example file (the gate that catches a secret
     leak at PR time, not audit time)
  5. sample_jsonl_present — sample.jsonl is non-empty (required
     positional arg for verify.py)
  6. verifier_entry_point_importable — verify.py parses cleanly
     (compile() without executing; full import lives in the
     operator's manual dispatch because it pulls validator/ deps)

Exit codes: 0 = PASS, 1 = FAIL, 2 = unexpected error. JSON output
via --json for CI consumption; human-readable by default. The
operator's full conformance run is workflow_dispatch (next commit).

Live: all 6 checks pass on the current submodule state.

* test(gate): add 22 tests for provider_verifier_gate (6 checks + aggregate + CLI)

The 6 static checks each get a pass + fail test (15 tests). The
aggregate run_gate gets pass + fail + to-dict tests (3 tests). The
CLI main() gets pass + fail exit code tests (2 tests). The
fixture_verifier_dir fixture creates a tmp_path with a valid
provider.json, sample.jsonl, and a stub verify.py (parseable, not
the real one) so tests don't depend on the submodule's heavy deps.

Notable coverage:

  - The 'real key detected' test puts a 26-char sk- key into the
    fixture and asserts the check fails with 'Real API key' in the
    detail. This is the load-bearing test for the secret-leak gate.

  - The 'syntax error' test writes a broken verify.py and asserts
    the check fails with 'syntax error' in the detail. Catches the
    case where a refactor introduces a SyntaxError that wouldn't
    surface until the operator's manual run.

  - The 'wrong type' test (provider.json is a dict, not a list) and
    the 'not a dict' test (an entry is a string) catch the two
    shapes of malformed config the static check needs to handle.

  - The 'verify_submodule_missing' test points at a non-existent
    path and asserts the actionable 'git submodule update' detail.

  - The CLI tests exercise the exit codes (0 vs 1) and the --json
    flag, which is what the CI workflow will consume.

22/22 pass.

CHIT trail unsigned-local.

* feat(ci): add .github/workflows/provider-verifier.yml — the static gate

The MiniMax-Provider-Verifier is the conformance gate for adding a
new provider to the Mavis model cascade. It REQUIRES real API calls
(--api-key, --base-url, --model) so it cannot run in CI without
exposing secrets.

This workflow runs the static half
(pmoves/tools/provider_verifier_gate.py) on every PR that touches
the relevant paths. 6 checks, no API calls, JSON output consumed
by the step summary + a PR comment on FAIL.

The status check name is verifier-gate; merge-gate.yml references
it (so a FAIL blocks merge). workflow_dispatch is the operator's
full-conformance path: the gate runs the static checks + the
operator runs verify.py locally with the API keys.

Workflow structure (lesson #12/13 from the post-merge fix — the
test file lives separately so a future edit doesn't silently break
the gate):

  triggers:
    - pull_request to main on Pmoves-MiniMax-Provider-Verifier/**,
      pmoves/tools/provider_verifier_gate.py, the test file, and
      the workflow file itself
    - workflow_dispatch for manual full-conformance runs

  permissions: {}   # minimal; no scopes the job doesn't need

  concurrency:      # one concurrent run per ref; cancel on
                    # re-runs of the same PR (matches existing
                    # branch-protection-drift.yml pattern)

  steps:
    1. actions/checkout@<SHA> with submodules: recursive
       (so Pmoves-MiniMax-Provider-Verifier/ is on disk)
    2. actions/setup-python@<SHA> Python 3.12 (matches the
       verifier's pyproject.toml requires-python)
    3. Best-effort install of the verifier's runtime deps
       (numpy, openai, megfile). The static gate doesn't need
       them; the full dispatch path does.
    4. Run the static gate, capture the JSON output, write to
       GITHUB_STEP_SUMMARY, exit 0/1 on PASS/FAIL.
    5. On FAIL, post a PR comment with the common-cause list
       (per the comments pattern in codeql.yml / claude.yml).

The shell pipeline uses \set +e ... ; RC=\True ... set -e\ because
the gate's \py ... --json\ exits 1 on FAIL and we need to
capture BOTH the output and the exit code. This is the same
pattern as the post-merge fix in PR #2569 (lesson #13: \set -e\
without \pipefail\ masks publisher failures).

CHIT trail unsigned-local.

* refactor(gate): take verifier_submodule as a parameter, not a module global

The 6 check functions used module-level globals (VERIFIER_SUBMODULE,
PROVIDER_CONFIG, etc.) resolved at import time. The first test
pass wrote the helper to work against the live submodule; the
test run then mutated gate.VERIFIER_SUBMODULE between tests, but
the check functions kept reading the cached paths from the
global — so tests for missing-syntax, missing-config, etc. read
the LIVE files, not the fixture files. 15/22 tests failed for
that reason.

Refactor: each check function now takes verifier_submodule: Path
as an explicit parameter. run_gate() resolves the path (default
to the live submodule) and passes it through. Tests pass a
tmp_path fixture; the CLI takes --verifier-submodule as a flag.
The helper's behavior is unchanged on the live submodule.

Also adds 15 workflow-glue tests in
pmoves/tests/test_provider_verifier_workflow.py (lesson #12/13
from PR #2569 — workflow YAML needs code review, not just unit
tests; these tests parse the YAML and assert on the patterns
that bind the workflow to the helper's contract: trigger block,
permissions, concurrency, job name, pinned checkout, submodules:
recursive, --json flag, exit code capture + propagation, py
alias). 15/15 pass.

CHIT trail unsigned-local.

* docs(verifier): update PROVIDER_VERIFIER_GATE.md — Gate in CI is now wired

The previous version of this doc said 'not yet wired — see
Follow-up below'. This slice wires it: the static half runs on
every PR via .github/workflows/provider-verifier.yml; the full
conformance run is the operator's manual step (where the API
keys live in env.shared, not in a workflow file).

The updated 'Gate in CI' section has two subsections:

  - 'The static half' (PR-time, no API calls): the 6 checks
    table + the workflow's behavior on FAIL + the load-bearing
    status check name (verifier-gate, which merge-gate.yml
    consumes)
  - 'The full conformance run' (operator's manual step): the
    exact command + why verify.py can't run in CI (F-07 supply
    chain forbids exposing the API keys) + the workflow_dispatch
    trigger is wired for a future 'CI-issued conformance report'
    mode that the operator hasn't enabled yet

The doc now reflects the current state: the static gate is the
PR-time check, the full run is the operator's local step, and the
two-piece design is intentional (not a TODO).

CHIT trail unsigned-local.

* feat(merge-gate): wire verifier-gate into the merge-decision + skipped-aware

The provider-verifier.yml workflow's job name is 'verifier-gate'.
For a FAIL on that gate to block merge, the merge-decision step
in merge-gate.yml must (a) include 'verifier-gate' in its needs
list, and (b) treat the workflow's result correctly.

Two changes to merge-gate.yml:

  1. Add 'verifier-gate' to the merge-decision needs list, and
     to the failure-check conditional. Now a FAIL on the gate
     blocks merge, like the other 3 hard gates.

  2. Treat 'skipped' as a pass, not a failure. The
     provider-verifier workflow has a paths: filter; a PR that
     doesn't touch the relevant paths runs the workflow as
     'skipped' (not 'success' or 'failure'). A naive
     'skipped == failure' check would block every PR that
     doesn't touch the verifier — a regression. The merge-decision
     only treats 'failure' as blocking; 'skipped' and 'success'
     both pass.

A comment in the workflow explains the skipped = pass design so
the next person who reads the conditional doesn't 'fix' it back
to the naive interpretation.

Also adds 2 cross-workflow tests in
pmoves/tests/test_provider_verifier_workflow.py (the existing
file already covers the provider-verifier.yml contract; this
extends it to the cross-workflow binding):

  - test_merge_gate_references_verifier_gate: asserts the
    merge-gate workflow's text mentions 'verifier-gate' (the
    load-bearing reference for the gate to actually block merge)
  - test_merge_gate_handles_verifier_gate_skipped: asserts
    the merge-decision does NOT treat 'skipped' as failure
    (catches the naive 'if result == skipped block' anti-pattern
    that would regress every PR that doesn't touch the verifier)

15 -> 17 tests; all 17 pass.

CHIT trail unsigned-local.

* fix(workflow): add issues:write to static-gate job for the PR-comment step

The PR-comment step uses actions/github-script, which needs the
default GITHUB_TOKEN scope to call issues.createComment. With
workflow-level 'permissions: {}' the token has no scopes, so
the step would fail with a 403 permissions error.

Fix: add a job-level permissions override on the static-gate
job: 'issues: write'. The workflow-level {} remains for the
other steps; the override is the minimum scope that lets the
comment step post.

Test: test_workflow_job_has_issues_write_for_comment_step
asserts the job-level permissions.issues is 'write'. Catches
the case where a future edit removes the override (or moves
the comment step without adjusting the permissions).

17 -> 18 tests; all 18 pass.

CHIT trail unsigned-local.

* agnote: CLAIM+RELEASE the provider-verifier-ci-gate slice

The CI gate for the MiniMax-Provider-Verifier is now wired: the
static half runs on every PR via .github/workflows/provider-verifier.yml;
the full conformance run is the operator's manual step. merge-gate.yml
treats verifier-gate as a hard gate (with the paths-filter 'skipped'
edge case handled so non-touched PRs still merge).

7+ commits in the 3-stacked group (functional / docs / fix-up):
  1. gate helper
  2-4. tests (22 unit + 15 workflow + 1 refactor that takes
        the submodule as a parameter so tests don't depend on
        global state)
  5. doc update
  6. merge-gate wire-up
  7. permissions fix (issues:write at the job level so the
     PR-comment step has the scope it needs)

The CLAIM row + GRAPHITI_MARK trailer (Mavis::PROVIDER-VERIFIER-CI-
GATE-CLAIM-RELEASE::2026-08-19) appended per the append-only
discipline. The next agent's trust anchor.

CHIT trail unsigned-local. LEARNINGS file follows in the next
commit.

* learnings: add provider_verifier_ci_gate_LEARNINGS.md — capture the 5 patterns from this slice

5 patterns captured in §2:

  2.1 Two-piece gate — why not a single CI workflow that runs
      verify.py. The verifier needs real API calls; the F-07
      supply-chain note forbids exposing the keys in a workflow
      file. The gate is the static half (PR-time, no API calls,
      catches the cheap failures) + the full half (operator's
      manual step on the local node, where env.shared holds the
      keys).

  2.2 Workflow permissions: workflow-level {} + job-level
      issues: write. The PR-comment step needs issues: write;
      the static check needs no scopes. Job-level override is
      the minimum scope that lets the comment step post.

  2.3 The skipped == failure regression — paths-filter
      workflows don't always run. A naive merge-decision that
      treats skipped as failure blocks every non-touched PR.
      The test asserts the absence of the naive check so a
      future 'fix' doesn't regress.

  2.4 Test the binding, not just the parts (lesson #12/13 from
      PR #2569). 15 workflow-glue tests assert the patterns
      that bind the workflow to the helper's contract: trigger
      block, paths filter, permissions, concurrency, job name,
      pinned checkout, --json flag, RC=\True exit code capture
      + propagation, py alias.

  2.5 Helper refactor: parameter, not module global. The first
      version used module-level globals resolved at import
      time; tests that mutated the global kept reading the
      cached paths, so 15/22 tests failed for that reason. The
      fix: each check function takes verifier_submodule: Path
      as a parameter. The helper's behavior on the live
      submodule is unchanged.

Cross-refs in §4 point at every file this slice touched, plus
the related PRs (2586, 2589, 2590, 2568, 2569, 2612) and the
operator (three-body delivery=Mavis, control=DARKXSIDE,
memory=this trail).

CHIT trail unsigned-local.

* docs(bootstrap): add provider-verifier to the Known Roads table

A cold-start agent that doesn't know about the gate would skip
it on a PR that touches the verifier submodule. The Known
Roads table in BOOTSTRAP.md is the cold-start discoverability
surface; the verifier is a row alongside secrets-funnel,
health-summary, fleet-status, sign-trail, and docs-reconcile.

Two rows added:

  - 'Run the provider-verifier static gate' — the PR-time check
    (no API calls, 6 checks, FAIL blocks merge via merge-gate).
    This is the entry a Mavis-class agent should reach for when
    asked 'does the new provider config look right?'

  - 'Run the provider-verifier full conformance' — the
    operator's manual step. The entry is operator-only
    (requires real API keys from env.shared) and explicit
    about that constraint, so a future cold-start agent
    doesn't try to invoke it.

The two rows together reflect the two-piece design from
PROVIDER_VERIFIER_GATE.md and the LEARNINGS file (2.1): the
static half on every PR + the full half on the operator's
local node. The Known Roads pattern (one canonical command per
need) is the same as the existing rows.

CHIT trail unsigned-local.

* test(install): pin the operator's full-conformance run path

The static half of the gate runs in CI; the full half is the
operator's manual step. Two surfaces describe the manual step:
pmoves/docs/operations/PROVIDER_VERIFIER_GATE.md and BOOTSTRAP.md.
If a future refactor changes the operator's command, the doc
drifts from reality and the operator hits a 'command not found'.

6 tests pin the contract:

  - test_verifier_help_lists_required_flags: invokes
    'py verify.py --help' and asserts the 4 load-bearing flags
    (--providers, --output-dir, --model, --base-url) are
    listed. Skips cleanly if verify.py can't import (the
    operator's local env needs the verifier's runtime deps).

  - test_requirements_txt_has_runtime_deps: asserts the 6
    runtime deps (jsonschema, loguru, megfile, numpy, openai,
    tqdm) are in requirements.txt. The operator's install
    is 'pip install -r requirements.txt'; if a dep is dropped
    from there, the operator's install silently fails.

  - test_pyproject_python_version_requirement: parses the
    pyproject.toml's requires-python and asserts it's >= 3.12.
    The CI workflow's python-version: '3.12' setup-python step
    must be at least as new; this test catches a future bump
    that drops the floor.

  - test_gate_doc_has_full_run_section: asserts the gate doc
    has a 'Gate in CI' section + mentions 'full conformance'.
    The exact section name is the contract.

  - test_bootstrap_md_has_provider_verifier_rows: asserts the
    BOOTSTRAP Known Roads table mentions both 'provider-verifier'
    and the helper path. Cold-start discoverability.

  - test_bootstrap_and_gate_doc_agree_on_invoke: asserts both
    surfaces invoke the helper via the exact same path
    ('py pmoves/tools/provider_verifier_gate.py'). Catches drift
    between the two discoverability surfaces.

5/6 pass (1 skip when verify.py can't import — graceful fallback).

CHIT trail unsigned-local.

* fix(ci): repair three defects that would have blocked every PR in the repo

Three review findings, all confirmed against source. The first two are
repo-wide blockers.

1. merge-decision listed `verifier-gate` in `needs`, but `needs` resolves only
   job IDs in the SAME workflow -- verifier-gate is the display NAME of the
   `static-gate` job in provider-verifier.yml. Verified by parsing the file:

     jobs defined : merge-gate, python-tests, docker-build-validation,
                    hardening-validation, action-pin-validation, merge-decision
     needs        : ..., verifier-gate
     undefined    : ['verifier-gate']

   An unresolvable `needs` makes the workflow invalid, so `merge-decision` --
   a REQUIRED status check on main -- never reports, and every PR in the repo
   sits blocked on a check that cannot run. Reverted merge-gate.yml to main's
   version: the four in-workflow jobs, no cross-workflow reference.

   To actually gate on the verifier, add its check to branch protection's
   required list. That is an operator action and cannot be expressed through
   `needs`. Left undone rather than faked.

2. The static gate ran `py pmoves/tools/provider_verifier_gate.py` on
   runs-on: ubuntu-latest. `py` is the Windows launcher; setup-python exposes
   the interpreter as `python` (the pip step directly above already uses it).
   It would exit 127, set verdict=FAIL, and block every PR matching the path
   filter without running a single check -- a gate that fails closed for the
   wrong reason is still a gate nobody can pass.

3. check_example_keys_are_placeholders used `if key and key != PLACEHOLDER`,
   so a falsy value skipped the comparison entirely. An empty api_key passed
   this check while the required-fields check passed on mere presence, so the
   aggregate could report PASS on an example violating its own stated
   invariant. Now compares against the placeholder directly, with a distinct
   message for the empty case. Verified:

     correct placeholder  passed=True
     EMPTY string         passed=False
     null                 passed=False
     missing key          passed=False
     real-looking key     passed=False

Rebase note: .claude/mcp.json conflicted because this branch carries the
minimax pin from #2612 while main gained the hostinger fork correction from
#2615. Both are correct and independent, so the inventory line is the UNION of
the two, not a pick. Re-validated as JSON.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(ci): two more fabricated pins — caught by the gate #2622 just landed

The action-pin gate merged in #2622 failed this PR on its first run, on the
workflow this PR adds:

  [ERROR] provider-verifier.yml:72   actions/setup-python@65d7f1bbcb94  no such commit
  [ERROR] provider-verifier.yml:114  actions/github-script@ed9884d31165 no such commit

and verifier-gate itself died at "Getting action download info" for the same
reason, before any step ran -- the exact mechanism #2622 documented, now
reproduced on a third workflow.

Repinned to the SHAs the rest of the repo already uses, both verified to
resolve:

  setup-python   5fda3b95a4ea...  # v7.0.0
  github-script  3a2844b7e9c4...  # v9.0.0

One correction worth recording, because it nearly went the wrong way. I first
read `git/ref/tags/v9.0.0` as `d746ffe35508` and concluded the repo's existing
`3a2844b7` pin was inaccurate. It is not: v9.0.0 is an ANNOTATED tag, so that
ref returns the tag OBJECT, which dereferences via git/tags/<sha> to commit
3a2844b7. Comparing a tag-object SHA against a commit SHA reports a mismatch
that does not exist. Anything auditing pins has to dereference; action_pin_audit
already does, which is why it reported 0 version-comment warnings on a clean
tree.

action_pin_audit now passes: 230 pins across 60 workflows, exit 0.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* test(provider-verifier): the tests asserted the defects, so fix them too

The ratchet flagged 4 new failures after the previous commit. Three were tests
asserting the behaviour that was just fixed -- they were written to match the
implementation rather than the requirement, so they passed while holding the
defects in place. A test that locks in a bug is worse than no test: it makes
the fix look like the regression.

  test_merge_gate_references_verifier_gate
    asserted merge-gate.yml MUST name verifier-gate -- the cross-workflow
    `needs` that invalidates the workflow and takes the required
    merge-decision check down with it. Inverted: it must NOT.

  test_merge_gate_handles_verifier_gate_skipped
    asserted the skipped-as-pass branch for a job that is no longer a
    dependency. Replaced with a check that merge-decision requires
    == "success" rather than merely rejecting "failure" (#2622).

  test_workflow_uses_py_alias_for_python
    asserted `py`, justified as "the GitHub-bundled alias" that "the other
    workflows in this repo use". Both halves are false, measured across
    .github/workflows/: ZERO workflows invoke bare `py`; 27 use
    python/python3. Inverted, and the docstring now carries the measurement.

  test_verifier_help_lists_required_flags
    shelled out to `py` directly. Now sys.executable.

21 passed, 3 skipped.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Mavis <Mavis@pmoves.local>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant