Skip to content

fix(templates): stop shipping the dd-* demo fleet and a dead GitHub repo list as starting points - #1934

Merged
AndriiPasternak31 merged 7 commits into
devfrom
feature/1931-hide-dd-templates
Aug 2, 2026
Merged

fix(templates): stop shipping the dd-* demo fleet and a dead GitHub repo list as starting points#1934
AndriiPasternak31 merged 7 commits into
devfrom
feature/1931-hide-dd-templates

Conversation

@vybe

@vybe vybe commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Fixes #1931

A fresh install's Library advertised 14 local templates — 11 of them the dd-* VC
due-diligence demo fleet
— plus 6 GitHub repos last pushed Dec-2025/Jan-2026 that no
install had ever overridden. Both were presented as real starting points. They are not: the
dd-* agents only work as a deployed set, and the GitHub list was a bundled default nobody
curated. The catalog now shows 3 starters (sage, scout, scribe) and 0 GitHub
repos.

What changed

  • hidden: true on the 11 dd-* templates via the existing bug: agent template catalog surfaces internal test/canary fixtures + a broken stub as real templates #1513 mechanism — omitted
    from GET /api/templates, still creatable by id. The fleet stays deployable as a set
    through a bundled manifest.
  • DEFAULT_GITHUB_TEMPLATE_REPOS = []. The constant is kept, not deleted — TMPL-001's
    None-vs-[] fallback, routers/settings.py, and the Settings "defaults" badge all
    reference it, and trinity-enterprise#14 will repoint the same seam.
  • Every bundled template directory must now declare hidden: (CI-enforced). The runtime
    default is deliberately unchanged — flipping it would turn a forgotten key into a silent
    absence.
  • Library + Settings empty states now name an action the user can actually take.
  • .gitleaks.toml: YOUR_TOKEN allowlisted (a regexes entry, not paths). The new
    manifest's DEPLOY comment reuses the sanctioned Authorization: Bearer YOUR_TOKEN placeholder
    already in research-network.yaml:10, which trips gitleaks' default curl-auth-header rule;
    a regexes entry suppresses only that placeholder, so config/manifests/ stays scanned for
    real secrets (a paths block is a pre-scan file skip and would not).

Deliberate decisions (not scope drift)

  • The marketplace-first empty state was an explicit human decision that superseded the
    plan.
    The abilityai/abilities marketplace and its create-agent wizards are presented as
    the recommended path; Settings → GitHub Templates + owner/repo is secondary. The
    plan had these the other way round.
  • Settings.vue is AC-fix: add missing logging_config.py to backend Dockerfile #4 completion, not scope drift. The Library's new hint pointed at a
    destination that itself dead-ended — Settings said "No templates configured. Add a GitHub
    repo above or reset to defaults"
    while the Reset button is :disabled in exactly that
    state and the shipped default list is now empty. Fixing the origin without the destination
    would have left the dead end one click further in.
  • The manifest was PROMOTED from docs/demos/vc-due-diligence/system-manifest.yaml, not
    invented
    , and is named vc-due-diligence.yaml on purpose. Deployed agent names are
    built as f"{system_name}-{short_name}" (system_service.py:382) and
    dd-lead/CLAUDE.md hardcodes its roster as nine vc-due-diligence-dd-* literals. Any
    other manifest name therefore ships 11 healthy containers whose orchestrator 404s on every
    delegation — with green CI. A new guard
    (tests/unit/test_1931_manifest_roster.py) catches exactly that. The promoted copy also adds
    the dd-lead → specialist permissions the demo left to a manual step and drops three keys
    that were dead or harmful there.
  • Tag-alongs, named so they aren't mistaken for drift: the Library.vue page-level hint
    copy; the ent124 test widening from a fixed path to a glob; the
    test_real_catalog_surfaces_the_three_starters rename and its now-inert ordering clause; and
    a third test file (test_1931_empty_github_defaults.py) that converts a read-only
    "these two branches are byte-identical" argument into a running proof.

Two intended side-effects

  • GET /api/templates now makes zero outbound GitHub calls on a cold metadata cache.
  • The new manifest is inert data, not a triggerBUNDLED_MANIFEST_PATH hard-codes
    config/manifests/default-system.yaml (system_seed_service.py:83), so nothing auto-deploys
    it.

Measured outcome

before after
Visible local templates 14 3 (sage, scout, scribe)
GitHub repos in the shipped default list 6 0
Bundled dirs omitting hidden: 25 0

25 bundled template directories total: 3 visible, 22 hidden.

Verification

  • Full tests/unit (verify stage): 6012 passed / 18 skipped
  • Full tests/unit (ship stage re-run): 6010 passed / 18 skipped / 2 failed — both failures are
    environmental, not branch-caused; see "Ship-stage re-run caveats" below.
  • 3-file #1931 set + the 2 modified test files, re-run at ship stage after the docstring fix:
    69 passed (test_1931_catalog_intent.py, test_1931_empty_github_defaults.py,
    test_1931_manifest_roster.py, test_local_templates_listing.py,
    test_ent124_default_system_seed.py)
  • npm run build: clean
  • /verify-local --skip-agent: PASS; integration 70 passed / 13 skipped / 2 deselected
  • Image-level proof: /app/config/manifests/vc-due-diligence.yaml is present in the built
    backend image, 6493 B byte-match, parses to name = 'vc-due-diligence', 11 agents, zero
    vc-demo
    .

Guard mutations all fail RED

Mutation Result
Drop a dd-* hidden: key 3 red
Add a visible directory visible-set pin red
Manifest name:vc-demo roster red
Break a name inside .claude/commands/ roster red

Ship-stage re-run caveats (host environment, disclosed)

The ship-stage full-suite re-run was done on a host whose environment differs from the verify
host. Two artifacts, neither branch-caused:

  1. 4 files could not be collectedtest_1771{a,b,c}_*_properties.py need hypothesis,
    which is a declared test dep (tests/requirements-test.txt:26hypothesis==6.161.5)
    simply not installed on this host. Excluded from the re-run; all four are untouched by
    this branch.

  2. 2 failedtest_1081_physical_meter.py::TestPerAgentMeter::test_available_floors_at_zero_over_ceiling[sqlite]
    and ::TestDisjointBulkMeter::test_max_respects_506_clamp[sqlite]. Shared-state
    interference from a second, unrelated pytest process running concurrently on the same host

    (these tests exercise the fleet-wide max_parallel_tasks_ceiling setting, which feat(capacity): admin-configurable system-wide ceiling + agent-settings UI for max_parallel_tasks #506
    deliberately reads uncached from shared system_settings). Evidence they are not
    branch-caused:

    • tests/unit/test_1081_physical_meter.py is byte-identical to origin/dev
      (blob 622c4eb4);
    • both named tests pass in isolation;
    • the whole file passes in isolation — 14 passed.

    CI runs the suite without a competing process, so this should not reproduce there.

Pre-existing failure — disclosed, not branch-caused

tests/unit/test_1069_voip_call_path_param.py::test_flat_path_params_are_agent_name_not_name
imports get_flat_dependant (line 214), which fastapi 0.141.1 removed.
tests/requirements-test.txt:33 floats fastapi>=0.115.0 with no upper bound, so whether
this test passes depends on which fastapi a host resolves.

This branch leaves tests/unit/test_1069_voip_call_path_param.py, src/backend/routers/voip.py,
and tests/requirements-test.txt byte-identical to origin/dev — verified by blob hash — so
it cannot be branch-caused either way.

Honest correction to the hand-off note: on the host used for this ship run the test
passes (fastapi 0.136.1 resolved, where get_flat_dependant still exists). The red is
real but environment-dependent, not "fails on this host". The unbounded pin is the actual
defect and is listed as a follow-up below.

Follow-ups (listed, deliberately not filed)

Merge order

Four files collide with sibling PRs. No rebase is needed todayorigin/dev is unmoved at
the base commit. First-merged wins; the others rebase.

File Note
docs/memory/requirements/core-agent.md §4.1 all three PRs; clean 2-bullet insert at the …since #843. anchor
docs/memory/feature-flows.md index row
docs/memory/feature-flows/template-processing.md
tests/unit/test_local_templates_listing.py one bottom-anchored hunk at line 312 of 368

🤖 Generated with Claude Code

@github-actions

github-actions Bot commented Aug 2, 2026

Copy link
Copy Markdown

⚠️ Nightly unit-suite check skipped — merge conflict against dev.

Resolve by running git merge dev locally and pushing the result. The next nightly run will re-test once the conflict is gone.

@AndriiPasternak31 AndriiPasternak31 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

APPROVE/review + /validate-pr. Zero critical findings, 3 warnings, none blocking.

I verified the load-bearing claims from source rather than from the PR body, and mutation-tested the guards. Everything holds. One correction below, in the PR's favour.

Verified independently

Claim Method Result
.gitleaks.toml is a regexes entry, not paths read the full [[allowlists]] block; grepped every +/- line '''YOUR_TOKEN''' appended to regexes (:65); paths (:50-61) byte-untouched, so config/manifests/ stays scanned for real secrets. The only added lines naming config/manifests are comment text inside the regexes entry. gitleaks CI passing is the empirical half.
vc-due-diligence.yaml is inert grepped every config/manifests reference repo-wide; read _resolve_manifest BUNDLED_MANIFEST_PATH (system_seed_service.py:83) is a string literal; the resolver reads exactly two sources (env override + that literal). Zero glob/iterdir/listdir/scandir in the three system modules. Every other reference is packaging (Dockerfile COPY, compose :ro).
hidden: is catalog-only — the fleet stays deployable traced all read paths get_local_templates() (:529) is the sole exclusion point. get_local_template(), crud._resolve_local_template(), and system_service._preflight_template all resolve by directory name and never read the flag.
Manifest's name/roster claims read the cited code base_name = f"{system_name}-{short_name}" (system_service.py:383). dd-lead/CLAUDE.md = 13 occurrences, 9 distinct names — exactly the 9 permissions.explicit edges, dd-intake correctly absent. The other ten dd-*/CLAUDE.md: 0 cross-agent refs, so 9 edges is the complete topology, not an omission.
explicit = full replacement read configure_permissions :455-459 clears every system agent absent from the map before applying. Comment is accurate.
Catalog outcome audited all 25 dirs on disk VISIBLE = {sage, scout, scribe}, 22 hidden, 0 undeclared — matches the PR's table exactly.
Tests ran them 69 passed, reproducing the stated number.
Guards aren't vacuous mutated the tree, ran, restored ✅ Drop dd-founder's hidden:3 red. Manifest name:vc-demoroster red. Both match the mutation table. Tree restored, 69 green again, git status clean.
Test hygiene ran tests/lint_sys_modules.py ✅ 194 violations vs baseline 240 — no new.

Two things the PR under-claims, worth stating: Invariant #13 needed no code change — MCP list_templatesclient.listTemplates()GET /api/templates → the same single filter, so all three surfaces converge (only the tool description needed editing, which it got). And the upgrade path is fine — both compose files bind-mount ./config/agent-templates:/agent-configs/templates:ro, so existing installs pick up the hidden: flags on restart rather than needing a volume reset.

The new empty state's CTA is real, not decorative: useTemplate({id:'github-custom'}):initial-templateform.template (CreateAgentModal.vue:347), and :447 explicitly exempts the sentinel from the "template doesn't exist → reset to blank" guard, with :365 auto-focusing the repo input.

Correction: the merge-order table over-warns

I simulated squash-merges in a throwaway worktree rather than predicting from hunk overlap:

Scenario Conflicts
dev + #1935, rebase #1934 docs/memory/requirements/core-agent.md only
dev + #1935 + #1936, rebase #1934 docs/memory/requirements/core-agent.md only

template-processing.md auto-merges; feature-flows.md and test_local_templates_listing.py never conflict. The one conflict is a pure adjacent-insert — #1935's #1900 read-path bullet vs this PR's two bullets at the same §4.1 anchor, zero semantic overlap. Resolution is keep both, either order. I resolved it that way, completed all 7 commits clean, and re-ran: 84 passed (this PR's 69 + #1935's additions to the same files). The two PRs' guards coexist, and #1935's own bullet independently states "Hidden templates (hidden: true) stay resolvable by id: the barrier reads the name, never the flag" — corroborating this PR's central claim.

Flagging this down because an inflated conflict list invites a careless bulk resolution on the one file that actually needs care.

Warnings (non-blocking)

  1. The superseded demo manifest keeps its live footgun. docs/demos/vc-due-diligence/system-manifest.yaml:46 still carries prompt:, and system_service.py:1143 writes it fleet-wide via db.set_setting("trinity_prompt", …). The SUPERSEDED header names the hazard and the README is repointed — real mitigation — but the header itself says the file "is NOT guarded against drift," and removal is one line. Its sibling claims do check out: auto_start has zero reads, and the only folders read is per-agent (:193). Fine as the filed follow-up; noting the one-line option.

  2. #1936's prose goes stale on rebase. Its §4.1.1 says "all 14 visible bundled templates failed the same four HARD checks" — after this PR, 3 are visible, contradicting §4.1 two bullets above. Its tests are safe (GUARDED_TEMPLATES is an explicit tuple, deliberately not a live hidden query, with a comment citing #1931). Docs-coherence fix for whoever lands second.

  3. Issue #1931 carries both status-in-progress and status-ready. Auto-promotion writes status-in-dev and leaves status-ready as drift. Worth dropping before merge.

Suggestions

  • requirements/security.md §20.8 doesn't name the new YOUR_TOKEN entry. Consistent with precedent (it never listed your-api-key/your-domain\.com either), so optional.
  • test_1931_manifest_roster.py mutates sys.path at import while its sibling explicitly argues against that, and its assert paths runs at collection time (called in the @parametrize at :72) so a missing config/manifests/ errors rather than skips. Cosmetic.

Forward-looking

Once #1911 (ent#126) lands, GET /api/systems/manifests globs config/manifests/ and surfaces each manifest as a one-click UI install with a sets_prompt warning — so vc-due-diligence.yaml stops being merely inert data and becomes a listed, installable catalog entry. That reads as AC #2's ideal fulfilment rather than a defect, and I checked the interaction: _preflight_template resolves through _resolve_local_template, which ignores hidden, so all 11 dd-* preflight valid: true rather than showing up broken. No action — just don't carry "inert" forward as an assumption after #1911.

Process

All 24 CI checks pass, including gitleaks, prod-image-smoke, regression diff, the sys.modules lint, e2e, and 6 pytest runs (3 seeds × base/head). Security greps clean — the only "email" hit is @pytest.mark.parametrize and the 5 "IP" hits are GitHub-logo SVG path coordinates. Zero new os.getenv() (no config-packaging gap); config.py is a modification already covered by Dockerfile:83, and the new manifest ships via the wholesale Dockerfile:109 COPY ../../config/manifests. architecture.md:222 names only default-system.yaml for the seed and stays accurate with a second manifest present, so skipping it was right. All 6 ACs trace to a named guard.

The detail I'd single out: test_real_catalog_surfaces_the_three_starters noticed its own ordering clause could only go vacuous, not red, on the very change it guarded — said so in the docstring, and replaced it with a non-vacuous priority == 20 assertion. That's the failure mode most PRs ship silently.

Merge order: land #1935 first, expect exactly one conflict in core-agent.md, resolve keep-both.

Eugene Vyborov and others added 7 commits August 2, 2026 14:28
…ange (#1931)

Rule #1: land the requirements/flow delta before the code.

requirements/core-agent.md
- §4.1: two bullets — catalog intent is DECLARED, not defaulted (every bundled
  directory must set `hidden:`; the runtime default is deliberately unchanged,
  because flipping it turns a forgotten key into a silent absence); and demo
  fleets ship hidden but stay deployable via a bundled manifest, with the
  system-name/short-name coupling to dd-lead's hardcoded roster spelled out.
- §4.2.1: the shipped GitHub default list is empty and why; None-vs-[] now
  differ only in the `source` badge; `github:owner/repo` create is untouched.
- §4.5: the GitHub-zero empty state — marketplace-first, then the owner/repo
  CTA, then the role-branched curation hint; plus the precedence rule that
  makes it mutually exclusive with the page-level empty state.

feature-flows
- library-page.md: the GitHub-zero placeholder, the 4-row precedence truth
  table, and a #1931 revision-history row.
- template-processing.md: the sort no longer "orders starters ahead of the
  rest" — after this change there is no rest; component is Library.vue.
- mcp-orchestration.md: drop the agent-ruby example that will never again
  appear in list_templates.

No architecture.md change: it documents the mechanism (the `hidden:` filter,
the None-vs-[] fallback, the section render), and no mechanism changes here.
Adding a catalog-contents paragraph would give requirements/core-agent.md §4 a
second home, against that file's own editorial rule #1.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ult list (#1931)

A fresh install's Library showed 14 local templates — 11 of them the VC
due-diligence demo fleet — plus 6 GitHub repos last pushed Dec-2025/Jan-2026
that no install had ever overridden. The visible catalog is now the 3 starters
we actually stand behind.

  get_local_templates() on the real catalog: 14 -> 3 (sage, scout, scribe)
  GitHub entries on a default install:        6 -> 0
  bundled dirs omitting `hidden:`:           14 -> 0
  bundled dirs total:                        25 -> 25  (nothing deleted)

Catalog
- 11 x `hidden: true` on dd-*/template.yaml, placed and commented to match the
  11 directories that already declare it. Not deleted, not moved: it is a demo
  we still run, `local:dd-lead` stays creatable by id, and the resolver never
  reads `hidden` (verified: get_local_template("local:dd-lead") still resolves).
- 3 x `hidden: false` on sage/scout/scribe — the AC asks a new directory to
  DECLARE its catalog intent, so the declaration has to be mandatory.

- DEFAULT_GITHUB_TEMPLATE_REPOS = []. Every consumer already tolerates it (five
  existing unit tests stub exactly this), and the one path that could have
  regressed does not: recreating an agent made from `github:abilityai/agent-ruby`
  routes through get_github_template, whose `if repo in DEFAULT_...` branch and
  "Dynamic" fallback are byte-identical two-line bodies. Zero behavioural delta.
  Emptying a BROWSE list deletes no data and stops no agent, so the #1638
  "mutable code default read at action time" lesson does not bite here.
  Side-effect, intended: GET /api/templates now makes zero outbound GitHub calls
  on a cold metadata cache, where it previously blocked on up to six.

Still deployable as a set
- config/manifests/vc-due-diligence.yaml, PROMOTED from
  docs/demos/vc-due-diligence/system-manifest.yaml — not authored. The system
  name and short names are load-bearing: deployed names are f"{name}-{short}"
  and dd-lead/CLAUDE.md hardcodes its roster as `vc-due-diligence-dd-*`, so a
  tidier `vc-demo` + `founder` would deploy 11 healthy containers whose Deal
  Lead reaches nobody. Added the nine dd-lead -> specialist permissions (the
  manual post-deploy step the old copy told you to run by hand); dropped
  `prompt:` (overwrites trinity_prompt), `auto_start:`/`folders:` (not read by
  parse_manifest) and per-agent `resources:` (each dd-* template.yaml overwrites
  it at creation). dd-lead is listed FIRST so a creator hitting the default
  10-agent quota loses a specialist, not the orchestrator.
- The old copy gets a SUPERSEDED banner and the demo README points at the new
  location; keeping it (rather than moving) preserves any existing link, and the
  banner is what stops the two drifting.

Tests
- test_1931_catalog_intent.py (new, dependency-free): every bundled directory
  declares `hidden:`, it is a real bool, and the visible set is pinned to
  sage/scout/scribe. Own file so `import yaml` stays out of
  test_local_templates_listing.py's import block, and because
  _build_local_template's `bool(data.get("hidden", False))` destroys exactly the
  present-vs-absent information this asserts on. The RUNTIME default stays
  visible-by-default on purpose — flipping it turns a forgotten key into a
  silent absence, which is the worse failure.
- test_1931_manifest_roster.py (new): validate_manifest over the glob, plus the
  assertion that would have caught the naming trap. It anchors on the SHORT NAME
  (`<prefix>-<short>` in a deployed template's CLAUDE.md must equal that
  manifest's resolved name), not on the manifest's own name — the latter goes
  vacuously green on precisely the rename it exists to catch. Verified to fire
  on both a system rename and a short-name rename, and to find zero false
  positives across all four bundled manifests.
- test_local_templates_listing.py: `dd-` joins the visible-prefix ban;
  test_real_catalog_surfaces_starters_ahead_of_suite renamed and reworked — its
  `if dd_positions:` clause could now only go vacuous, so it asserts the
  priority: 20 mechanism instead, with the ordering clause generalised and
  labelled inert.
- test_ent124_default_system_seed.py: the two on-disk checks widened from
  default-system.yaml to a glob over config/manifests/*.yaml, parametrised so a
  failure names the manifest. They are properties of any bundled manifest.

Docs
- config/agent-templates/README.md: the dd table moves out of "Starter
  templates" into its own "Demo fleet" subsection under "Not starting points",
  both demo manifests are named, and the authoring rule becomes the
  intent-declaration contract.
- mcp-orchestration.md + mcp-server create_agent description (Invariant #13):
  drop the `github:abilityai/agent-ruby` example that will never again appear
  in list_templates.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…es (#1931)

With DEFAULT_GITHUB_TEMPLATE_REPOS emptied, a default install has zero GitHub
templates — and the section was wrapped in `v-if="githubTemplates.length > 0"`,
so it silently VANISHED. requirements §4.5 promises "per-kind empty states
teach the next action"; that promise was being honoured only by accident,
because the count had never been zero before.

Library.vue
- New `noTemplatesAtAll` computed over the WHOLE /api/templates response (both
  sources). The section now renders on `!noTemplatesAtAll` — not
  `githubTemplates.length > 0 || !noTemplatesAtAll`, whose first disjunct is
  provably dead since githubTemplates is a filter over templates. The
  page-level "No templates configured" block is UNCHANGED and keeps sole
  ownership of the wholly-empty case; the 4-row truth table is inlined at the
  computed, and exactly one empty state renders in every row.
- Placeholder card, marketplace-first (operator decision): the
  abilityai/abilities marketplace + create-agent wizards lead, because they
  exist today and a fresh install's Settings panel does not. Then the
  secondary "already have a repository?" action, then the curation hint.
- The CTA is `useTemplate({ id: 'github-custom' })`, CreateAgentModal's own
  sentinel for the free-form owner/repo option — deliberately NOT
  `useTemplate(null)`, which is byte-identically the Blank Agent button two
  sections up and would land the user on the wrong option under a different
  label. The sentinel arrives via the existing `initial-template` prop and is
  explicitly exempted from that component's unknown-template reset, so no
  CreateAgentModal change is needed.
- Only the curation hint branches on role (`useRole()`), mirroring
  LibrarySkillsSection.vue on this same page — the templates half must not
  ship the opposite convention to the skills half. The ACTION is offered to
  both roles so a non-admin is never left holding only an admin-only path.
- Tag-along: the page-level hint said "Configure GitHub templates in config.py",
  which is not an operator surface and is now empty by design.

Settings.vue — the destination must not dead-end
  The Library's own hint sends an admin to Settings → GitHub Templates, where a
  fresh install said "…or reset to defaults" next to a Reset button that is
  :disabled in exactly that state and would now reset to the same empty list.
  This change would otherwise satisfy the AC on the Library and newly violate
  it one click away. Two strings: drop the impossible action from the empty
  row, and stop badging an empty set as "Using defaults".

Verified: `npm run build` clean. No e2e spec asserts any changed string —
smoke.spec.js matches the headings 'Library' / 'Agent Templates' with
exact: true (the new card's heading is "No GitHub templates configured"), and
settings-tabs.spec.js only route-mocks /api/settings/github-templates.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…change (#1931)

/update-tests + /sync-feature-flows tail pass.

The riskiest claim in this change was verified only by READING: that emptying
DEFAULT_GITHUB_TEMPLATE_REPOS cannot break an existing agent created from
`github:abilityai/agent-ruby`, because get_github_template's `if repo in
DEFAULT_...` branch and its "Dynamic" fallback are byte-identical two-line
bodies. That is the sibling-path rule — a guard proven on one codepath has to
be proven on every path reaching the same behaviour — so it is now proven by
RUNNING:

  test_1931_empty_github_defaults.py
  - an unconfigured github: id still resolves with the list empty
  - the emptied and configured paths return the IDENTICAL template dict
  - get_all_templates() with [] makes ZERO outbound metadata fetches
    (the intended side-effect: no ThreadPoolExecutor, no HTTP, no PAT read)
  - counter-test: a CONFIGURED repo is still fetched, so the assertion above
    cannot go vacuously green if fetching breaks entirely
  - the shipped constant is []

Verified to fail when the list is refilled. `tests/lint_sys_modules.py` green
(monkeypatch.setitem, no bare sys.modules assignment); order-independent
(_metadata_cache cleared per load, since it is module-global and survives).

Flow docs — two surfaces the plan had not named:
- platform-settings.md (TMPL-001): the section said admins configure repos
  "replacing the hardcoded config.py list" and that no-DB-config falls back to
  "[...from config.py...]". Both now misleading. Records the empty default,
  that None and [] produce the same catalog and differ only in the badge, that
  Reset-to-Defaults reverts to empty (and is already :disabled there), that
  create capability is untouched, and the zero-outbound-calls side-effect.
- system-manifest.md: a new bundled manifest is DATA, NOT A TRIGGER
  (BUNDLED_MANIFEST_PATH is hard-coded to default-system.yaml; nothing globs
  the directory) — the basis on which an 11-agent / ~40 GB manifest was safe to
  add. Plus the ent124 glob widening and the new roster guard.
- feature-flows.md: one Recent Updates row.

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

Review findings on this branch's own new tests.

1. The roster guard walked `CLAUDE.md` only. A literal collaborator name is
   just as load-bearing — and breaks just as silently — in a slash command,
   and the sibling ent#239 check already treats `.claude/commands/` as a
   first-class shipped surface. Restricting the walk left four real literals
   unguarded in the bundled corpus:
     sage/.claude/commands/request-research.md          -> acme-scout
     demo-analyst/.claude/commands/briefing.md          -> research-network-researcher
     demo-analyst/.claude/commands/request-research.md  -> research-network-researcher
   Widened to CLAUDE.md + .claude/commands/*.md + .claude/skills/**/*.md via a
   deduped, sorted `_prompt_files()` helper. Token pattern unchanged.
   Corpus: 13 -> 28 matching tokens across 4 manifests, zero offenders.
   Mutation-proved twice: renaming the manifest `name:` goes red (as before),
   and breaking a name inside a .claude/commands file now goes red too — the
   coverage that did not exist before.

2. `test_shipped_default_is_empty` did a bare `sys.path.insert(0, ...)` in the
   test body: a permanent, un-undone global side-effect (one duplicate entry
   per session) that `tests/unit/conftest.py` already makes unnecessary.
   Dropped; the test still passes standalone.

3. Dropped an unused `import yaml` inside the roster test, and corrected a
   comment claiming `_metadata_cache` "survives across loads" — each
   `exec_module` builds a fresh module, so the `.clear()` is defensive only.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The `test_manifest_resolved_names_satisfy_hardcoded_rosters` docstring said
restricting the walk to `CLAUDE.md` left "four" real literals unguarded. It is
three. Verified twice:

    grep -rlE "acme-scout|research-network-researcher" config/agent-templates/

returns 6 files, of which exactly 3 are not `CLAUDE.md` —
`demo-analyst/.claude/commands/{briefing,request-research}.md` and
`sage/.claude/commands/request-research.md` — and an occurrence count confirms
one match per file. The parenthetical that follows already enumerated three;
only the count word was wrong.

Docstring-only. No assertion, scan surface, or behaviour changes.

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

gitleaks' default `curl-auth-header` rule flags the
`-H "Authorization: Bearer YOUR_TOKEN"` line in the DEPLOY header comment of
config/manifests/vc-due-diligence.yaml (entropy 3.121928). It is a
documentation placeholder, not a credential, and is verbatim-identical to the
pre-existing line in config/manifests/research-network.yaml:10 — that one never
tripped the scanner only because the workflow scans the PR commit range, not the
whole tree. So this PR followed the house convention rather than introducing a
new practice; the manifest comment is left untouched.

Add YOUR_TOKEN to the existing `regexes` allowlist beside the other
CLAUDE.md-sanctioned placeholders (`your-api-key`, `your-domain.com`).
Deliberately NOT a `paths` entry: per the note already in this file, a path
allowlist is a PRE-SCAN file skip and would stop config/manifests/ being scanned
for real secrets, whereas a targeted regex suppresses only this placeholder.

Verified locally with the CI-pinned gitleaks 8.30.1 over the same commit range:
1 leak before, "no leaks found" after; a planted ghp_ PAT under
config/manifests/ is still detected.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants