Skip to content

feat(role-loader): CRD → file projection (proposal 010 PR-3) - #77

Merged
flg77 merged 1 commit into
mainfrom
role-sync/crd-to-files
May 14, 2026
Merged

flg77 merged 1 commit into
mainfrom
role-sync/crd-to-files

Conversation

@flg77

@flg77 flg77 commented May 14, 2026

Copy link
Copy Markdown
Owner

Summary

Third PR of proposal 010 — the Python mirror of PR-2's Go-side file watcher. When role_sync.role_source is crd or mirror, the new acc.role_crd_loader.RoleCRDProjector polls the K8s API for AgentCollective resources and writes their spec.roleDefinition to roles/<id>/role.yaml. The existing acc.role_loader.RoleLoader file watcher picks it up automatically — no new code path in the agent hot loop.

Inert by default: runs only when role_source is crd or mirror. Agents in standalone mode (the default) see no change and don't pay the kubernetes dependency cost.

What's new

acc/role_crd_loader.py (~330 LOC):

  • CRDClient Protocol so tests inject a fake without a live cluster.
  • KubernetesCRDClient lazy-imports kubernetes only on first use. In-cluster config preferred; kubeconfig fallback for laptop dev.
  • RoleCRDProjector polls every poll_interval_s (default 30 s) and writes files atomically (*.tmp + os.replace). Two layers of idempotency: in-memory cache + on-disk sentinel-stripped content comparison.
  • Sentinel header on every generated file:
    # Generated by acc.role_crd_loader from CRD
    # AgentCollective: <ns>/<name>
    # Source-of-truth: CRD (role_sync.role_source=crd or mirror).
    # …
    

Test plan

  • 19 unit tests in tests/test_role_crd_loader.py — all green locally:
    • _strip_sentinel edge cases (4)
    • project_one idempotency on cache + disk + content paths (7)
    • project_once batch behaviour + exception swallowing (4)
    • polling lifecycle start/stop/idempotent (3)
    • field-translation surface (1)
  • Integration on acc1 (operator's call): apply an AgentCollective CR, point an agent at it with ACC_ROLE_SOURCE=crd, verify roles/<name>/role.yaml appears within 1 poll interval and carries the sentinel header.

Out of scope

  • Wiring the projector into the agent bootstrap (this PR only ships the building block). PR-4 (mirror mode + conflict events) is where both directions go live together and the bootstrap wires them up.
  • Updating existing agents to run the projector — kept inert behind the flag.

Diff size

Area Lines
Production code 330
Tests 280
Docs (CHANGELOG) 30
Total 675

Production is under PR-3's 600-LOC budget; tests bring it over but are mostly fixture/setup code.

Proposal 010 reference

Vault path: 010 - Bi-directional file-CRD sync for role definitions.md. Status: Signed off 2026-05-14. PR-1 (#75) + PR-2 (#76) already merged.

🤖 Generated with Claude Code

Python mirror of PR-2's Go-side watcher.  When role_sync.role_source
is "crd" or "mirror", the new RoleCRDProjector polls the Kubernetes
API for AgentCollective resources and writes their spec.roleDefinition
to roles/<id>/role.yaml.  The existing acc.role_loader file watcher
then picks up the write naturally — no new hot-path code in agents.

Design choices:

* Optional kubernetes dep — never imported at module load.  The
  KubernetesCRDClient lazy-imports kubernetes on first use; if the
  import fails (agents running role_source=files) the error path is
  explicit and recovery is a config flip.  Agent containers stay slim
  by default.

* Protocol-based CRDClient — production impl wraps
  CustomObjectsApi; tests inject FakeCRDClient.  Keeps unit tests
  fast and cluster-free.

* Atomic writes — file.tmp + os.replace so the agent's file watcher
  never reads a half-written file.

* Idempotency in two layers: in-memory _last_written cache + on-disk
  content comparison (sentinel-stripped).  Prevents mtime spam under
  steady state.  Critical for mirror mode (PR-4) to not oscillate.

* Sentinel header on every generated file points back at the source
  CRD + carries a UTC timestamp.  Operators who cat the file see
  why it's there.  Header is stripped before content comparison so
  timestamp updates don't trigger spurious rewrites.

Test coverage: 19 unit tests in tests/test_role_crd_loader.py.
Covers _strip_sentinel edge cases, project_one idempotency on
in-memory + on-disk paths, atomic-write cleanup, empty-payload skip,
namespace+name in header, project_once batch behaviour, exception
swallowing on client errors, polling start/stop lifecycle (including
no-start safety), and field-translation surface.

KubernetesCRDClient itself is not unit-tested — it requires a live
cluster and is covered by integration tests on acc1.

Total: +675 LOC across 3 files (production ~330 + tests ~280 + docs).
Well under PR-3's 600-LOC production budget.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@flg77
flg77 merged commit b2c9d31 into main May 14, 2026
@flg77
flg77 deleted the role-sync/crd-to-files branch May 14, 2026 20:11
flg77 added a commit that referenced this pull request Jun 14, 2026
…77)

Brief-08: deliver the acc-pkg package toolchain as a UBI10 podman image
with a thin host wrapper, so users run build/inspect/install/eval/list
with zero local Python or `acc` install (mirrors Containerfile.cli/acc-cli).

- container/production/Containerfile.acc-pkg: minimal UBI10 python-312
  image; deps are pydantic+pyyaml only (catalog fetch is stdlib urllib);
  cosign v2.4.3 baked in for verify/install of signed packs; runs uid 1001.
- acc-pkg (repo-root wrapper): podman run --rm, binds CWD at /work. Maps
  the image uid onto the caller (podman keep-id:uid=1001 / docker --user)
  so build artifacts + installs are owned by the user, not a subuid.
  Defaults ACC_PACKAGES_ROOT into ./.acc/packages so installs persist past
  the --rm container.
- docs/CONTRIBUTING-ROLE.md: two-path "install the toolchain" section
  (container wrapper / Python editable).

Verified on acc1 (podman 5.6.0): image builds (acc 0.3.34); full chain
build -> inspect -> install --allow-unsigned -> eval runs offline against
the capital-markets family source. Build tarball_sha256 reproduces the
published @acc/capital-markets-roles@0.1.1 (49f43fcb...); eval surfaces
6 behavior + 5 safety + curated (the eval-autocopy bundle). quay push
deferred — acc1 not logged into quay.io (folds into the brief-06 wave).

Co-authored-by: flg <flg@acc1.ic3net.internal>
Co-authored-by: Claude Opus 4.8 (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.

1 participant