Skip to content

feat(governance): wire Skills + MCP into roles + Cat-A A-017/A-018 (Phase 4.3) - #7

Merged
flg77 merged 1 commit into
mainfrom
feat/phase-4.3-role-mcp-skill-wiring
Apr 29, 2026
Merged

feat(governance): wire Skills + MCP into roles + Cat-A A-017/A-018 (Phase 4.3)#7
flg77 merged 1 commit into
mainfrom
feat/phase-4.3-role-mcp-skill-wiring

Conversation

@flg77

@flg77 flg77 commented Apr 29, 2026

Copy link
Copy Markdown
Owner

Summary

Connects PR 4.1 (`acc/skills`) and PR 4.2 (`acc/mcp`) to the cell. After this PR, a role can declare which skills and which MCP servers it may reach, and the membrane (`CapabilityGuard`) enforces those whitelists every time `CognitiveCore` dispatches.

Surface added

`RoleDefinitionConfig` — six new fields

Field Default Role
`allowed_skills` `[]` (fail-closed) A-017 whitelist
`default_skills` `[]` Subset advertised in the LLM prompt
`max_skill_risk_level` `MEDIUM` Risk ceiling enforced by A-017
`allowed_mcps` `[]` (fail-closed) A-018 whitelist
`default_mcps` `[]` Subset advertised in the LLM prompt
`max_mcp_risk_level` `MEDIUM` Risk ceiling enforced by A-018

Note the inverse default vs `allowed_actions`: empty = deny-all on the new surface (skills + MCPs are explicit opt-in), where empty `allowed_actions` remains unconstrained for back-compat.

`acc/governance_capabilities.py` — `CapabilityGuard`

Stateless Python evaluator mirroring `CatAEvaluator`'s enforce/observe contract. Lives in Python (not OPA) because skill / MCP invocations are decision-level events inside one agent's `process_task` — an OPA subprocess hop per invocation would dwarf the actual check.

  • A-017 `check_skill_invocation` — three checks composed: whitelist → `requires_actions ⊆ allowed_actions` → risk ceiling.
  • A-018 `check_mcp_invocation` — same three plus the manifest's per-tool gate (`is_tool_allowed`).
  • CRITICAL hits set `decision.needs_oversight=True` so the caller can enqueue an `OVERSIGHT_SUBMIT` alongside the invocation (EU AI Act Art. 14).

`CognitiveCore` — registry kwargs + new entry points

  • `init` accepts `skill_registry` and `mcp_registry`; constructs `CapabilityGuard(enforce=cat_a_enforce)` from compliance config.
  • `invoke_skill(skill_id, args, role)` — guarded entry; raises `SkillForbiddenError` on A-017 block, fires `ALERT_ESCALATE`.
  • `invoke_mcp_tool(server_id, tool, args, role)` — guarded entry; raises `MCPToolNotFoundError` on A-018 block.
  • `build_system_prompt` — appends "Available skills" / "Available MCP servers" blocks when `default_` is non-empty AND aligned with `allowed_`. Legacy roles with no capability wiring see exactly the same prompt as before.

`acc/agent.py` — registries plumbed

`_build_skill_registry()` + `_build_mcp_registry()` always run at agent startup (so role hot-reload can toggle whitelists from `[]` → non-empty without restart). Errors during discovery are logged; the registry stays usable but empty.

Roles

  • `roles/_base/role.yaml` — six new fields with documented fail-closed defaults.
  • `roles/coding_agent/role.yaml` — wired to `allowed_skills=[echo]` / `allowed_mcps=[echo_server]` so the existing `coding_split` compose profile exercises the wiring end-to-end.

Rego declarative parity

`regulatory_layer/category_a/constitutional_rhoai.rego` gains `deny_skill_not_whitelisted` (A-017) and `deny_mcp_not_whitelisted` (A-018) for audit visibility and future RHOAI Gatekeeper use. The header note pins the Python guard as the source of truth for runtime enforcement.

Smoke tests (all green locally)

  • `RoleDefinitionConfig` defaults validate; Literal risk-level fields reject typos at config-load time.
  • Both registries load: 1 skill, 1 MCP server.
  • `CapabilityGuard` enforce-mode: empty whitelist denies, risk ceiling denies, manifest tool gate denies, allowed path passes.
  • `CapabilityGuard` observe-mode: every "would block" returns `allowed=True` with `reason='observed:...'`.
  • `CognitiveCore.invoke_skill` enforce-mode: A-017 raises `SkillForbiddenError`, increments `cat_a_trigger_count`, fires `ALERT_ESCALATE`.
  • `CognitiveCore.invoke_skill` happy path: returns adapter dict.
  • `build_system_prompt` advertises skills + MCPs only when `default_*` is set; legacy roles unchanged.

Existing test suite: 145 unit tests across `test_config` / `test_role_store` / `test_guardrails` / `test_compliance` pass unchanged. TUI test failures observed on this branch (9) are pre-existing on main with this branch's changes stashed — unrelated.

Test plan

  • `from acc.governance_capabilities import CapabilityGuard` imports cleanly.
  • `pytest tests/test_config.py tests/test_role_store.py tests/test_guardrails.py tests/test_compliance.py -q --no-cov` → all pass.
  • On a coding_agent container: confirm `build_system_prompt` includes the "Available skills: echo" line.
  • With `ACC_CAT_A_ENFORCE=true`: invoke a skill via the test harness on a role with empty `allowed_skills` — should raise `SkillForbiddenError`.

Out of scope

  • LLM-output → `invoke_skill` parser wiring (the `[ACTION: skill_id ...]` regex bridge that maps tool calls to `CognitiveCore.invoke_skill`). Lands in PR 4.4 alongside the TUI Ecosystem screen.
  • OPA WASM rebuild — the Rego additions are documentation-only until the next bundle build.
  • `stdio` MCP transport — still reserved for a follow-up.

… A-017/A-018 (Phase 4.3)

Connects PR 4.1 (acc/skills) and PR 4.2 (acc/mcp) to the cell:
RoleDefinitionConfig grows the capability whitelist, CognitiveCore
gains explicit invoke_skill / invoke_mcp_tool entry points, and a new
Python CapabilityGuard enforces Cat-A A-017 (skills) and A-018 (MCP
tools) at decision time.

acc/config.py
  RoleDefinitionConfig:
    + allowed_skills:        whitelist (fail-closed; empty = no skills)
    + default_skills:        subset advertised in system prompt
    + max_skill_risk_level:  ceiling LOW < MEDIUM < HIGH < CRITICAL
    + allowed_mcps:          whitelist (fail-closed)
    + default_mcps:          subset advertised in system prompt
    + max_mcp_risk_level:    ceiling enforced by A-018

  Inverse default vs allowed_actions: empty = deny-all on the new
  surface (skills + MCPs are explicit opt-in), where empty
  allowed_actions remains unconstrained for back-compat.

acc/governance_capabilities.py (new)
  CapabilityGuard — stateless Python evaluator with the same
  enforce/observe contract as acc.governance.CatAEvaluator.  Rules
  live here (not in OPA) because skill/MCP invocations are
  decision-level events inside one agent's process_task; a
  subprocess hop per invocation would dwarf the actual check.

  check_skill_invocation (A-017): three checks composed —
    1. skill_id ∈ role.allowed_skills            (fail-closed)
    2. manifest.requires_actions ⊆ allowed_actions
    3. manifest.risk_level ≤ max_skill_risk_level
  check_mcp_invocation (A-018): same three plus —
    4. manifest.is_tool_allowed(tool_name)
  CRITICAL hits set decision.needs_oversight=True so the caller can
  enqueue an OVERSIGHT_SUBMIT alongside the invocation
  (EU AI Act Art. 14).

acc/cognitive_core.py
  CognitiveCore.__init__:
    + skill_registry: Optional[SkillRegistry]
    + mcp_registry:   Optional[MCPRegistry]
    + self._capability_guard: CapabilityGuard(enforce=cat_a_enforce)
  + invoke_skill(skill_id, args, role)          — guarded entry point
  + invoke_mcp_tool(server_id, tool, args, role) — guarded entry point
  build_system_prompt(): when role.default_skills / default_mcps is
  non-empty AND every entry is in the corresponding allowed_* list,
  appends an "Available skills" / "Available MCP servers" block listing
  id + manifest.purpose so the LLM knows the surface is callable.
  Legacy roles with no capability wiring see exactly their previous
  prompt (no extra block).

acc/agent.py
  + self._skill_registry = self._build_skill_registry()  (always built)
  + self._mcp_registry   = self._build_mcp_registry()    (always built)
  Both pass-through into CognitiveCore.  Errors during discovery are
  logged and the registry stays empty — one bad manifest must not stop
  the agent from booting.  Always-built so role hot-reload can toggle
  allowed_skills [] → non-empty without restart.

regulatory_layer/category_a/constitutional_rhoai.rego
  + A-017 deny_skill_not_whitelisted (declarative documentation)
  + A-018 deny_mcp_not_whitelisted   (declarative documentation)
  Header note pins the Python guard as the source of truth for runtime
  enforcement; Rego rules exist for audit visibility and future
  Gatekeeper integration on RHOAI.

roles/_base/role.yaml
  Adds the six new fields with documented defaults — fail-closed
  whitelists ([]), MEDIUM risk ceilings.

roles/coding_agent/role.yaml
  Demonstrator wiring: allowed_skills=[echo], allowed_mcps=[echo_server],
  default_skills/default_mcps mirror the allowed lists, MEDIUM ceilings.
  This is the role most likely to test the wiring end-to-end through
  the existing coding_split compose profile.

Smoke tests (run locally — all green):
  * RoleDefinitionConfig defaults validate, Literal types reject typos.
  * Both registries load (1 skill, 1 MCP server).
  * CapabilityGuard enforce-mode: empty whitelist denies, risk ceiling
    denies, manifest tool gate denies, allowed path passes.
  * CapabilityGuard observe-mode: every "would block" is tagged
    reason='observed:...' and allowed=True.
  * CognitiveCore.invoke_skill enforce-mode: A-017 raises
    SkillForbiddenError, increments cat_a_trigger_count, fires
    ALERT_ESCALATE.
  * CognitiveCore.invoke_skill happy path: returns adapter dict.
  * build_system_prompt: 'Available skills' + 'Available MCP servers'
    blocks render only when default_* is non-empty AND aligned with
    allowed_*.  Legacy roles unchanged.

Existing test suite: 145 unit tests across config / role-store /
guardrails / compliance pass unchanged.  TUI test failures (9) are
pre-existing on main with this branch's changes stashed — unrelated.

Unblocks PR 4.4 (TUI Ecosystem live tables + how-to docs); the LLM
output parser that maps [ACTION: skill_id ...] markers to invoke_skill
calls is also a 4.4 deliverable so this PR ships the enforcement
surface without prematurely binding the parser shape.
@flg77
flg77 merged commit 0f8b80a into main Apr 29, 2026
@flg77
flg77 deleted the feat/phase-4.3-role-mcp-skill-wiring branch April 29, 2026 20:45
flg77 added a commit that referenced this pull request Apr 30, 2026
…hedule infusion + bare excepts (PR-A) (#10)

User report (notes 30-Apr-2026 + screenshots): on a freshly launched
TUI the Ecosystem screen lists 30+ roles correctly, but every operator
action below the list is dead.

Audit traced four distinct bugs to one shared cause + one antipattern:

1. Skills / MCP SERVERS tables show "no skills loaded — see howto-..."
   guidance row even though skills/echo and mcps/echo_server exist on
   disk.
2. ROLE DETAIL panel never updates when a role row is highlighted or
   clicked — stays stuck on "Select a role row to view its full
   definition".
3. "Schedule infusion → Nucleus" button hint pinned at "Select a role
   first" forever; clicking the button does nothing.
4. Bare `except Exception: pass` blocks inside the row-select handler
   swallow every failure silently, so the symptoms above produce no
   log line operators could investigate from.

Root causes:

* `_roles_root() / _skills_root() / _mcps_root()` returned bare
  relative strings (`"roles"` etc.).  When the TUI launches outside
  the repo root (pip install entry point, container with
  WORKDIR=/app), those resolve against os.getcwd().  Some manifest
  dirs hit and others miss depending on cwd at process start —
  exactly the partial-resolution mismatch the screenshot shows.
* The row-select handler set `_selected_role` AFTER calling
  `_show_role_detail()`, so any failure inside the detail render
  silently dropped the selection state.  Combined with bare excepts,
  the operator saw no message and the Schedule-infusion button stayed
  disabled.

Fixes (acc/tui/path_resolution.py — NEW):
  resolve_manifest_root(env_var, default_dir_name) → absolute Path
  Resolution order:
    1. ACC_*_ROOT env var (absolute or expanded against cwd) IF the
       path exists; warns + falls through if env var points to a
       missing dir (no silent use of bad config).
    2. Repo-anchored: <repo>/<default_dir_name> computed from this
       module's filesystem location (Path(__file__).parent.parent.parent).
       Works in editable installs and container layouts.
    3. CWD-relative literal as last-resort fallback (preserves test
       harness behaviour).

Fixes (acc/tui/screens/ecosystem.py):
  * `_roles_root` / `_skills_root` / `_mcps_root` delegate to the new
    helper; return type widens str → Path (loaders accept both).
  * `on_data_table_row_highlighted` (NEW handler): cursor movement
    over a role row now populates ROLE DETAIL live, matching the UX
    every spreadsheet-style table provides.  Pre-PR-A the operator
    had to know to press Enter; now scrolling does the right thing.
  * `on_data_table_row_selected` rewritten: sets `_selected_role`
    BEFORE calling `_show_role_detail()` so a render failure cannot
    leave the Schedule-infusion button disabled.  Uses the new shared
    `_arm_infusion_button()` helper.
  * `_extract_role_name` static helper: pulls the role name string
    out of a Textual RowKey across the API surface variations
    different Textual versions present.
  * Every bare `except Exception: pass` replaced with
    `logger.exception(...)` so future regressions surface in the
    rotating TUI log file.
  * `on_button_pressed` calls `self.notify("Highlight or click a role
    row first", severity="warning")` when `_selected_role` is empty —
    the dead-click is now visible toast feedback rather than silent.

Tests (tests/test_ecosystem_screen_pilot.py — NEW, 10 cases all green):
  Path-resolution (3): env-var wins / repo anchor falls back / missing
    env path warned-and-skipped (NOT silently used).
  Pilot-driven (7):
    skills_table_populated_when_manifests_exist — fixture sets
      ACC_SKILLS_ROOT to tmp dir with one echo manifest, table shows
      a real row not the empty-state guidance row.
    mcps_table_populated_when_manifests_exist — same shape for MCPs.
    row_selected_handler_directly — synthetic RowSelected event,
      detail panel update captured via monkeypatched Static.update.
    row_selection_arms_infusion_button — _selected_role + button
      state both correct after handler dispatch.
    row_highlighted_handler_directly — cursor-driven path covered too.
    button_press_with_selection_dispatches_role_preload — happy path
      → RolePreloadMessage reaches the App.
    button_press_without_selection_notifies — sad path → notify call
      captured, no RolePreloadMessage dispatched.

Why synthetic events instead of pilot.press("enter"): Textual's
DataTable RowSelected dispatch depends on the widget being the precise
focus target at the moment of dispatch, which Pilot's harness can't
reliably reproduce in test mode (we tried both pilot.press and
action_select_cursor, both failed deterministically).  Constructing
DataTable.RowSelected / RowHighlighted directly and dispatching to the
screen's handler exercises exactly the same code path Textual would
invoke on a real keypress, with no flakiness.

Existing suite: 139 unit tests across config / role-store / guardrails
/ compliance pass unchanged (TestEd25519Validation deselected for the
lighthouse OpenSSL platform limit, as in PRs #7-#9).

Out of scope (planned for follow-ups per the approved plan):
* PR-A2 — in-TUI manifest upload (FilePicker modal).
* PR-B  — direct prompt pane + open PromptChannel Protocol.

Notes file: C:\Users\micro\Documents\Notes\Notes\Development\AgenticCellCorpus\ACC TUI\TUI Fixes.md
flg77 added a commit that referenced this pull request Apr 30, 2026
…ss operator input (#13)

First non-TUI implementation of the PromptChannel Protocol from PR-B.
Operators @-mention an ACC bot in Slack; the daemon dispatches the
message as TASK_ASSIGN, awaits the agent's TASK_COMPLETE, and posts
the reply back to the same Slack thread.

acc/channels/slack.py (NEW)
  SlackPromptChannel — owns its own nats.aio.client connection
    (the TUI's NATSObserver is view-coupled and can't be reused from
    a daemon process).  Wire format mirrors NATSObserver.publish so
    agents on the bus can't tell which channel a TASK_ASSIGN came
    from.
    .connect()     — open NATS conn + subscribe to acc.{cid}.task
    .send(prompt, *, target_role, target_agent_id=None) -> task_id
    .receive(task_id, timeout) -> PromptResponse
    .supports_streaming() -> False
    .close()       — cancel inflight + drain NATS
    Subscription callback fans only TASK_COMPLETE into the per-task_id
    Future registry; TASK_ASSIGN echoes + everything else is silently
    dropped.  Single registry (_inflight) — receive() is the sole
    eviction point, so set_result + lookup race-free.

  SlackDaemon — Slack-side glue.  Uses slack_bolt's AsyncApp +
    AsyncSocketModeHandler so the bot works behind firewalls (no
    public webhook).  Imports slack_bolt LAZILY inside .run() so the
    rest of the package stays light when the operator doesn't run
    Slack.
    Bot @-mention →
      strip <@U...> tokens via _MENTION_RE,
      optional "role=<name>" prefix via _ROLE_PREFIX_RE,
      channel.send → dispatched-notice say(),
      channel.receive → final reply say() in the SAME Slack thread
        (uses event.thread_ts when present, falls back to event.ts
        so top-level @-mentions start a new thread).
    Empty prompts / role= without body produce ⚠️ notices.
    Send / receive failures + timeouts produce ❌ / ⚠️
    notices that include task_id[:8] for cross-referencing audit logs.

  main() entry point — env-driven config (SLACK_BOT_TOKEN,
    SLACK_APP_TOKEN, ACC_NATS_URL, ACC_COLLECTIVE_ID,
    ACC_DEFAULT_TARGET_ROLE, ACC_SLACK_TIMEOUT_S).

acc/channels/__init__.py
  + re-exports SlackPromptChannel + SlackDaemon so
    `from acc.channels import SlackPromptChannel` works without
    knowing the submodule path.

pyproject.toml
  + [project.optional-dependencies] slack = [slack_bolt, aiohttp]
    (operator runs `pip install 'acc[slack]'` to enable the daemon)
  + [project.scripts] acc-channel-slack = "acc.channels.slack:main"

docs/howto-slack-channel.md (NEW)
  9-section operator guide:
    1. Prerequisites
    2. Slack app setup (app + bot scopes + Socket Mode + event subscriptions)
    3. Install
    4. Run
    5. Use it (default routing + role=X prefix + threading)
    6. Wire-protocol notes
    7. Troubleshooting matrix
    8. Out of scope (streaming, agent=X routing, slash commands)
    9. See-also cross-links to acc/channels/base.py + acc/channels/tui.py

Tests (tests/test_slack_channel.py — NEW, 20 cases all green):
  Channel-side (9):
    connect_subscribes_to_task_subject
    send_publishes_canonical_task_assign
    send_omits_target_agent_id_when_none
    receive_correlates_by_task_id
    receive_timeout_drops_listener
    close_cancels_inflight_and_drains
    on_message_ignores_non_task_complete
    supports_streaming_returns_false
    payload_to_response_handles_missing_fields
  Regex helpers (3):
    mention_regex_strips_user_id_tokens
    role_prefix_regex_captures_role
    role_prefix_regex_no_match_when_absent
  Daemon dispatch (8):
    app_mention_strips_bot_token_and_dispatches
    app_mention_role_prefix_overrides_default
    app_mention_empty_prompt_warns
    app_mention_role_prefix_without_body_warns
    app_mention_timeout_posts_warning
    app_mention_blocked_reply_renders_with_block_marker
    app_mention_uses_thread_ts_when_replying_in_existing_thread
    app_mention_send_failure_posts_error

Mocking strategy: nats.aio.client is mocked via monkeypatch on the
sys.modules entry so the channel's own `import nats` succeeds against
a fake.  slack_bolt is NOT imported by the daemon's _on_app_mention
path — that's why the daemon tests run without slack_bolt installed
(only `.run()` would need it, and we don't exercise that in unit
tests; manual testing per docs/howto-slack-channel.md covers it).

Combined regression footprint: 204 tests pass (20 new Slack + 184
existing PR-A/A2/B + PR #9 + core).  TestEd25519Validation deselected
for the lighthouse OpenSSL platform limit, as in PRs #7-#12.

Out of scope (separate small follow-up PRs):
* TASK_PROGRESS streaming into Slack (channel.supports_streaming()
  flips True once the protocol stabilises).
* Per-agent routing via "agent=<id>" directive.
* Slash commands (/acc <prompt>) — currently mention-only.
* Container packaging (operator runs the daemon as a Python process
  for now; production image follow-up later).
* TelegramPromptChannel + WhatsAppPromptChannel — same Protocol,
  different bot SDKs.

Notes file: C:\Users\micro\Documents\Notes\Notes\Development\AgenticCellCorpus\ACC TUI\TUI Fixes.md
flg77 added a commit that referenced this pull request May 31, 2026
…rue (v0.3.41) (#7)

Continuation of followup #51.

v0.3.40 enabled the reflection LOOP (`ACC_REFLECTION_INTERVAL_S`
default 600 in baseline compose + INFO log on boot). But
`_run_reflection_once` is ALSO gated on the per-role
`memory_reflection` flag, and:

  - `RoleDefinitionConfig.memory_reflection: bool = False` (default)
  - Zero role.yaml files flip it on (`grep -rn memory_reflection roles/`
    returns nothing)

So even with the loop firing every 600s, no role ever actually
consolidated. Two-layer silent gate.

Verified live on lighthouse 2026-05-31 post-v0.3.40:
  ACC_REFLECTION_INTERVAL_S=60, boot log shows "memory_reflection:
  enabled interval=60s role=assistant"; sent a smoke task → episodes
  rose from 5 to 6 → waited 70s for the cycle → memory_notes stayed 0.
  Reflection loop firing, but `_run_reflection_once` early-returns.

Fix: flip the default from False to True. Roles that genuinely don't
want reflection (arbiter is a plausible candidate — pure cluster
control, doesn't reason on tasks) can opt out per role.yaml with
`memory_reflection: false`.

Cost: one extra LLM call per reflection_interval_s window per active
role. With the 600s default that's ~6 LLM calls/hour per agent — well
within the budget every existing role already consumes for tasks.

Test update: `test_role_memory_reflection_defaults_false` →
`test_role_memory_reflection_defaults_true` with opt-out assertion.

Full sweep: 2417 passed, 38 skipped, 0 failed.

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
flg77 added a commit that referenced this pull request Jun 29, 2026
…cryptography (#141)

Resolves the Python (pip) Dependabot alerts on flg77/acc-spearhead. Most were a
stale uv.lock (last generated 2026-06-14) whose pins lagged the already-permissive
pyproject constraints; one needed a constraint widening.

Lockfile refresh (constraints already allowed the fix — `uv lock --upgrade-package`):
  aiohttp   3.14.0 → 3.14.1   (#15-22, 8 alerts)
  pyjwt     2.12.1 → 2.13.0   (#9-13, 5 alerts)
  starlette 1.0.1  → 1.3.1    (#24-27, 4 alerts; transitive via fastapi)
  msgpack   1.1.2  → 1.2.1    (#37, high)
  joserfc   1.6.5  → 1.7.2    (#39; transitive via authlib)

Constraint widening (fix was outside the pin):
  cryptography 46.0.7 → 48.0.1 (#14/#23, two HIGH) — pyproject `<47`→`>=48.0.1,<49`.
  The Ed25519 arbiter sign/verify API is stable across 46→48; operator "pin and
  bump" one major at a time.

The lock also catches up on the speech/turbovec optional-deps added since 06-14
(faster-whisper/piper-tts/ctranslate2/onnxruntime/av/turbovec) — no security
content, just lock/pyproject reconciliation. No downgrades.

Not fixed here (separate handling):
  transformers (#1) — vulnerable `Trainer` not in our execution path; ST<5.0 blocks
    the 5.0.0rc3 fix → dismissed on GitHub with that reason (intent already in
    pyproject).
  torch (#7, low) — no patched release exists → dismissed "no fix available".
  npm console-plugin (7 alerts) — need npm/node (absent here) → fleet hand-off.

Verified with the bumped libs installed: signatures/spiffe 153 ✓, a2a/messenger/
slack/webgui/redis 168 ✓ (321 total, 0 failures).

Co-authored-by: flg <flg@acc1.ic3net.internal>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
flg77 added a commit that referenced this pull request Aug 23, 2026
…acks

The ACC Assistant now knows the @acc/supply-chain-roles pack and the guardrails
it must honor and surface when infusing/using it — so packaging work stays
governed end-to-end.

seed_context additions:
- list @acc/supply-chain-roles (packaging_engineer — RPM/DEB hardened, signed,
  attested builds) among the canonical first-party packs
- a general "rules of engagement — governance-sensitive packs" rule: before
  PROPOSE_INFUSE of such a pack, read its manifest rules of engagement
  (catalog_query / acc-pkg inspect) and state them back to the operator. The
  supply-chain pack is the reference case: builds in STAGING and only RECOMMENDS
  promotion (promotion to trusted/prod is CRITICAL, human-in-the-loop, never
  autonomous); signing keys referenced never placed in the workspace; hardening
  fail-closed; unjustified HIGH/CRITICAL CVE HOLDs the package. Propose the
  infuse + staging build, but route the promote decision to the operator.

Pack is live in the ecosystem catalog (acc-ecosystem v1.4.0, signed keypair +
keyless-OIDC); HowTo handed to acc-web (PR #55). Role source PR #7, tiers PR #8.

Co-Authored-By: Claude Opus 4.8 <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.

1 participant