particle-academy / change-council
A consumer-agent voting ledger for major package changes: a zero-dependency change detector for CI and pre-push, and a Laravel ledger with HITL initiation, two voting rounds, per-consumer silence policy, a hash-chained audit log, Ed25519-signed decision records and a lab UI built from Fancy componen
Package info
github.com/Particle-Academy/change-council
pkg:composer/particle-academy/change-council
Requires
- php: ^8.4
- ext-json: *
- ext-sodium: *
- illuminate/console: ^13.0
- illuminate/container: ^13.0
- illuminate/contracts: ^13.0
- illuminate/database: ^13.0
- illuminate/http: ^13.0
- illuminate/routing: ^13.0
- illuminate/support: ^13.0
Requires (Dev)
- laravel/framework: ^13.30
- pestphp/pest: ^4.0|^5.0
Suggests
- inertiajs/inertia-laravel: Renders the lab pages (Council/Index, Council/Show) through the default InertiaPageRenderer
Provides
None
Conflicts
None
Replaces
None
README
A consumer-agent voting ledger for major package changes. Before a package ships a change its consumers can feel (a third-party major, a raised platform floor, a removed export, a schema change, a deprecation), the agents that run the apps depending on it are asked, and every answer is kept.
Two halves, one repo:
- The detector,
council/check.mjs: zero-dependency Node that compares two git refs and reports what a consumer could feel. The same file runs in a local pre-push hook, in GitHub Actions on public repos, and behindphp artisan council:detect. - The ledger,
particle-academy/change-council: a Laravel package for a lab. Proposals, two voting rounds, per-consumer silence policy, owner decisions, an append-only hash-chained audit log, Ed25519-signed decision records, a JSON API, an Overwatch feed, and lab pages built from react-fancy.
Pre-1.0, and report-only. Phases 1 and 2 of the design. Nothing gates a release yet: the detector exits 0 whatever it finds, and decision records are written and verified but not required. The gate is Phase 3. Breaking changes land in minor releases; read the CHANGELOG.
The rules it encodes
- Silence is not consent. A required voter's silence escalates to the owner,
unless the owner has set that consumer to
proceed_and_notify(it proceeds, and the consumer's late vote is still recorded). - Agents petition; the owner decides. A whitelisted agent's finding or request, or an issue from a whitelisted author, opens round 1. Anything non-whitelisted waits for the owner with the initiating agent's pros and cons. No petition leaves first. Owner decisions are made in the authenticated lab UI only; no artisan command can record one.
- Replies are data. Strict JSON, bound to the round by nonce and digest, identified by the Genie-stamped sender, flagged for injection, rendered as text, never applied.
- The ledger never calls Genie. The lab's own agent (the clerk) sends the petitions it queues and pipes the replies back.
- The detector is a floor, not a ceiling. A behaviour change behind an unchanged signature is invisible to it. Declare those.
Quick look
node council/check.mjs # previous v* tag -> HEAD, human summary node council/check.mjs --json --out r.json # structured report
change-council detector 0.1.0 (REPORT-ONLY: Phase 1 never fails a release)
compare: v1.0.0 -> HEAD (3f2a9c1e0b7d) kind: library
MAJOR (2)
[platform-floor] composer:php ^8.3 -> ^8.4 (composer.json)
[third-party-major] composer:acme/http ^2.1 -> ^3.0 (composer.json)
...
use ChangeCouncil\Facades\Council; Council::authorizeOwners(); // COUNCIL_OWNERS=owner@example.com; an empty list allows nobody Council::routes();
Owner-only on purpose: on a host with open registration, "any signed-in user" is anyone. See the quick start.
Optional, and off by default: Council::notifyGenie() (or
COUNCIL_NOTIFY_GENIE=true) sends owner events to Genie's app inbox after
commit. These are a proposal awaiting the owner, initiation approved or
rejected, override, revoke, a closed tally and escalation. The notice carries refs and counts only, and a failure is
audited, never thrown. See the
quick start.
Docs
- Laravel quick start: install, authorization, pages, commands, API
- The detector: classes, waivers, surface snapshots, what it cannot see
- CI usage: GitHub Actions and pre-push, pinned SHA plus content hash, never in an id-token job
- Petitions and replies: initiation, the clerk loop, wire formats, tally and silence
- Decision records:
decision.v1, signing, verification, the audit chain - The Overwatch feed:
feed.v1, the signed webhook, open questions for civi - Lab UI: the pages, and how they were checked in a real browser
Requirements
- Detector: Node 22+ and git. PHP on PATH for the PHP API comparison (it says so in the report when it is missing).
- Ledger: PHP 8.4 with sodium, Laravel 13. inertia-laravel and react-fancy for the pages (both labs have them).
Testing
node --test "council/test/*.test.mjs" # detector: a must-fire and a must-not-fire repo per class vendor/bin/pest # ledger: hermetic, no network, a fake petition transport php tests/lint.php
License
MIT