Skip to content

v0.1.0-alpha.189

@github-actions github-actions tagged this 05 Jun 13:18
- **ai-agent: surface transport errors during `AnthropicProvider` streaming.** Streaming responses that failed mid-flight (network/transport errors) were previously swallowed; the provider now surfaces them to the caller so failures are observable rather than silent. (#1612, PR #1613)

- **CI: green the Integration suite under PHP 8.5 `#[\NoDiscard]` + `failOnWarning`.** `packages/audit/src/Query/AuditEventQuery.php` discarded the return values of the `#[\NoDiscard]` fluent builder methods `DBALSelect::fields()`/`condition()`; the return is now assigned at each call site (no behaviour change — the builder mutates in place). This, together with the OIDC full-kernel integration tests realigned to the `/oidc/*` + DB-backed-key contract from mission `oidc-flows-completion-01KSEFTP`, clears the last `ci/unit-tests` failures that had kept `main` red.

- **admin: bundle-aware entity edit form exposes per-bundle fields.** The admin edit/view form fetched the schema by entity type with no bundle, so a node's per-bundle fields (`body`, `blocks` for the `page` content type) never appeared and only core fields (title, slug, published) were editable. `GenericAdminSurfaceHost::handleSchema` now resolves the bundle for the schema (an explicit `bundle` in the payload, else read from the entity named by `id`) and passes it to the bundle-aware `SchemaController::show`, so the client needs no per-type bundle-key knowledge and non-bundled types are unchanged. The admin transport sends the entity id with the schema request; `useSchema` caches per type+id; `SchemaForm`/`SchemaView` fetch the bundle-scoped schema so the `body` rich-text field renders, edits, and saves through the existing bundle write path. The structured `blocks` field (no inline editor yet) round-trips as a hidden value. Verified end to end in a real browser: edit a page's body, save, the change is live on the public route, revert is live. (PR #1614)

- **page-builder: reconstruct stripped wrapper elements from the CSS contract.** `Waaseyaa\PageBuilder\Html\HtmlComponent` now repairs block wrappers that an upstream editor (WordPress / TinyMCE / Elementor) stripped from pasted HTML but whose own scoped `<style>` still describes them. When the component CSS declares a styled subject class (`... .card { ... }`) and uses it as the ancestor in descendant rules (`... .card .icon { ... }`), and that wrapper class is absent from the markup while a container holds a flat run of its descendant children, the wrappers are rebuilt from that contract alone, adding no content. Strictly gated (never fires on well-formed markup; only re-parents existing elements) and builder-agnostic. Turns a flattened card grid back into real cards. New `reconstructStrippedWrappers()` plus a CSS-contract parser; two new `ScopedHtmlComponentTest` cases against a synthetic, builder-neutral fixture. (PR #1614)

- **Classification & retention engine** (mission `classification-retention-engine-01KSEFTH`, spec `docs/specs/classification-and-retention.md`). A Nation can label any entity with a confidentiality/hold classification, inherit labels down a parent chain, gate reads by clearance, and drive retention from those labels. **WP01** shipped the `classification_label` field type (`classification_label` / `classification_inherited_from` / `classification_overridden_at` columns), the `ClassificationLabelDefinition` entity + nine seed labels, `LabelInheritanceResolver` + stock parent resolvers, and the PRE_SAVE `EntityLifecycleSubscriber`. **WP02** added the `RetentionPolicy` entity + migration; `ClassificationFieldAccessPolicy` (`AccessPolicyInterface` + `FieldAccessPolicyInterface` intersection, registered `#[PolicyAttribute(entityType: '*')]`) enforcing **hold-overrides-clearance** (C-004/FR-013 — a `hold-*` label forbids reads for any account without the `legal-hold-bypass` permission, even admin) then a clearance gate (`RoleBasedClearanceChecker` over `classification.role_clearance` config); `ClassificationLabelRegistry` (per-request cache); the `legal-hold-bypass` permission + `governance-viewer` role (`packages/field/defaults/classification.yaml`); and friendly `/api/classification/policies/*` JSON:API routes (read `governance-viewer,admin`; write `admin`). **WP03** added `ClassificationRetentionScheduleEntries` (purge 6-hourly, redact 6-hourly+30m, hold-scan daily) backing `PurgeJob` (deletes age-eligible matches, writes `retention.purge`), `RedactJob` (nulls `#[FieldTemplate(pii: true)]` fields, preserves identity/audit, writes `retention.redact`), and `HoldScanJob` (verification-only `hold_vs_purge` conflict detection) — all best-effort per policy (NFR-004) — plus the FR-015 kernel-composition integration test. **WP04** added the admin SPA editor at `/classification/policies` (`useRetentionPolicies` composable + list/detail pages using `SchemaForm`, "Governance" nav entry).

- **docs/audits**: `docs/audits/2026-05-l2-content-types-audit.md` — L2 content-types consolidation audit covering all 11 L2 packages. Result: 7 production-ready, 4 alpha-needs-hardening (attachment, groups, engagement, messaging), 0 dead. Filed by mission `l2-content-types-consolidation-01KSEFTX` WP01+WP02.

- **OCAP audit log substrate** (`packages/audit`, renamed from `analytics`): append-only unified event table (`audit_event`) spanning entity lifecycle, API requests, agent tool executions, MCP dispatches, and broadcast publications. `AuditEvent` + `AuditRetentionPolicy` entities; `AuditWriterInterface` (best-effort, never throws) bound to `AuditEventWriter`; `AuditQueryInterface` bound to `AuditEventQuery` (indexed on `account_uid`, `entity_type_id/entity_uuid`, `event_kind/created_at`). Five cross-cutting listeners (`EntityLifecycleAuditListener`, `ApiRequestAuditListener`, `AgentToolAuditListener`, `McpDispatchAuditListener`, `BroadcastAuditListener`) each with best-effort try-catch wrapping. JSON:API read endpoint `GET /api/audit/events` (filterable by `kind`, `account`, `entity`, `from`/`to` date range; default 50/max 500 per page; `_role: admin`). `bin/waaseyaa audit:prune --older-than=<ISO-8601-duration> [--kind=<glob>] [--dry-run]` CLI command with self-audit event (`audit.retention_pruned`) on each real execution. 17-case `AuditEventKind` enum. Closes gap-matrix A3 / DIR-004 (OCAP-by-architecture). (ocap-audit-log-substrate-01KSEFTF WP01–WP03)
- **mcp: FieldAccessPolicyInterface enforced in entity serializer** (M-A5 WP02, mission `per-record-ai-access-flagship-01KSEFT5`). `McpEntityFieldFilter` applies field-level access enforcement to MCP entity responses: forbidden fields are replaced by the canonical redaction marker `{"accessRestricted": true, "reason": "field_forbidden_for_account"}` rather than omitted, preserving audit lineage for AI callers. Open-by-default semantics maintained — Neutral and Allowed results expose the field unchanged. JSON:API omits forbidden fields; MCP redacts them — both surfaces honour the same policy but with surface-appropriate representations. `McpController` wires the filter into `EntityTools` via `setFieldFilter()`. Parity integration test `tests/Integration/PhasePerRecordAiAccess/McpJsonApiFieldParityTest.php` guards the FR-007 contract: removing the filter wiring causes the test to fail. FR-005, FR-006, FR-007, C-003, DIR-004 satisfied. (gap-matrix-A5)
- **Versioned blob media abstraction**: `GET /api/media/{uuid}/versions` and `GET /api/media/{uuid}/versions/{vid}` read-only JSON:API endpoints backed by `MediaVersionRepository` (WP01/WP02). New `Waaseyaa\Api\Media\MediaVersionReadModelInterface`, `ApiMediaVersionAdapter`, `MediaVersionResource` DTO, `MediaVersionController`, and `MediaVersionApiRouter`; two BuiltinRouteRegistrar routes gated by `_authenticated`; 403/404 distinction via `existsByVid()`. Admin SPA gains `useMediaVersions()` composable and `MediaVersionBrowser.vue` component rendered at `/media/{uuid}/versions`. (DIR-005, mission `versioned-blob-media-abstraction-01KSEFTJ` WP03/WP04)
- **Admin SPA: AI observability runs list, per-run detail with span tree, and replay action.** New pages at `/ai/observability/runs` (list with filter bar and pagination) and `/ai/observability/runs/[uuid]` (detail with cost/token summary, span tree, and replay). `useAiObservabilityRuns` and `useAiObservabilityRunDetail` composables consume `GET /api/ai/observability/runs` and `GET /api/ai/observability/runs/{uuid}` respectively. `RunFilterBar`, `RunListTable`, and `RunSpanNode` components. NavBuilder gains an "AI" section with a `/ai/observability/runs` entry above Operations. 26 new i18n keys. Vitest tests for both composables; E2E skeleton (skipped, deferred to CI). (#1415)
- **Admin SPA: MCP endpoint admin** — tool registry browser, per-tool detail with recent invocations, and server config viewer (read-only). (#1415)
- **Admin SPA: workflow guards matrix** visible at `/workflows/{id}` (M4A-5 Phase 1, mission `workflow-guards-readonly-01KSDS5W`, parent #1470). New `GET /api/workflow-definitions/{workflow_id}/guards` endpoint returns the `(bundle, transition, required_roles)` rows for a registered workflow, gated by `_role: admin` at the route level (NFR-001 — controller does not re-check). `Waaseyaa\Workflows\AuthoringRoleMatrix` gains `snapshot()`, `forWorkflow($workflowId)`, and `knownWorkflowIds()` accessors that flatten an optional `workflowGuards` constructor argument keyed by `[workflowId => [transitionId => list<role>]]` cross-producted with the configured bundles in deterministic order. `WorkflowServiceProvider` binds `AuthoringRoleMatrix` as a container singleton seeded from `EditorialTransitionAccessResolver::allowedRolesForTransition()` for each editorial transition (cycle 2 fix — without this binding the dashboard was dead code in production). New `Waaseyaa\Api\Controller\WorkflowGuardsController` (returns `{data: [...]}` on hit, JSON:API 404 envelope on unknown workflow id — FR-003), `Waaseyaa\Api\Http\Router\WorkflowGuardsApiRouter`, and a fourth `resolveOptional()` block in `ApiServiceProvider` that wires the matrix into the router only when bound (slimmed-down installs remain bootable). New `useWorkflowGuards()` Nuxt composable + `WorkflowGuardsTable.vue` inline section between the existing transition matrix and the dry-run form on `/workflows/{id}`; six new `workflow_guards_*` i18n keys. Phase 2 (edit) is deferred — see follow-up #1579 (M4A-5b) for the persistence ADR and `PUT` mutation surface. (#1470)
- **Admin SPA: queue dashboard now lists queued and in-flight jobs alongside failed** at `/queue`. `Waaseyaa\Queue\Transport\TransportInterface` gains `listJobs(int $limit, int $offset = 0, ?string $status = null): array` returning `{data: list<JobRow>, total: int}` (mandatory on all transport implementors; `@api`) — derived `status` per row is `'queued'` when `reserved_at IS NULL` and `'in_progress'` otherwise. Both shipped backends implement it: `DbalTransport` issues separate COUNT + SELECT queries against `waaseyaa_queue_jobs`, `InMemoryTransport` merges `$queues` + `$reserved` sorted by id. New abstract contract test `Waaseyaa\Queue\Tests\Contract\AbstractTransportContract` (in `packages/queue/tests/Contract/`) is shared by `DbalTransportContractTest` + `InMemoryTransportContractTest` and exercises empty / all-queued / queued+in_progress mix / status filter / limit-offset pagination / zero-limit / invalid-status across both backends. `GET /api/queue/jobs` now reads optional `?status=failed|queued|in_progress|all` (default `failed` — NFR-001 M4B backward compatibility preserved; the meta envelope shape is unchanged so the M4B `PhaseQueueAdmin` integration test passes UNCHANGED). `QueueController::__construct()` gains a nullable `?TransportInterface $transport = null` third arg; failed branch keeps the `FailedJobRepository` path, queued/in_progress branches delegate to `TransportInterface::listJobs()`, `all` merges failed-first-then-transport on a single page. When the transport is unbound (slimmed-down install), non-failed statuses gracefully fall back to the failed-only shape. `ApiServiceProvider` extends the queue `resolveOptional()` block to also resolve `TransportInterface` (optional, mirrors the M4B graceful-degradation pattern). Frontend `useQueueJobs()` returns a new `status` ref + `fetchJobs(page, perPage, status)` third arg (default `'failed'`); response row type is now the discriminated union `QueueJob = FailedJob | TransportJob` with an `isFailedJob()` guard. `pages/queue/index.vue` adds chip filter buttons above the table — failed chip keeps the M4B full-detail columns + retry/discard buttons, live chips render a lean `(id, queue, status pill, attempts, age)` table with NO retry/discard buttons (C-001 — retry/discard remain failed-only). New i18n keys: `queue_status_failed`, `queue_status_queued`, `queue_status_in_progress`, `queue_status_all`, `queue_age_seconds`, `queue_column_status`, `queue_column_age`. M4B follow-up landed as mission `queue-listjobs-transport-01KSDS5T` WP01. (#1576)
- **Admin SPA: notification channels dashboard** at `/notifications` with per-channel test send. New `Waaseyaa\Api\Controller\NotificationController` (`GET /api/notification/channels` listing every registered `ChannelInterface` keyed by type with its implementation FQCN; `POST /api/notification/channels/{type}/test` building a synthetic `TestRecipient` + `TestNotification` from the requesting admin's `_account` and calling `ChannelInterface::send()` inside a try/catch) routed through `NotificationAdminApiRouter` and gated by `_role: admin` (NFR-001). Per the M4B precedent, the controller never serialises a `\Throwable` across the JSON boundary — failures are extracted into `{type, status: "failed", message, exception_class}` (FR-010). `NotificationDispatcher` gains a single `channels(): array` accessor so the controller can read the dispatcher's configured channel map without exposing other state. New `/notifications` Nuxt page mirrors `/queue` shape; `useNotificationChannels` composable; `NotificationChannelRow` component with confirm-modal flow; "Operations" sidebar gains a `/notifications` link. Delivery log + per-channel enable/disable are deferred — the notification package does not yet carry the persistence; follow-up filed for that work. M4C WP01 of mission `notification-rules-admin-01KSDRNW`, closes audit C-L3-02 + C-L0-03. (#1472)
- **Admin SPA: scheduler dashboard** at `/scheduler` with manual task trigger and last-run history. New `Waaseyaa\Api\Controller\SchedulerController` (`GET /api/scheduler/tasks` listing every registered `ScheduledTask` with its `last_run_at` / `last_status` / `next_run_at`; `POST /api/scheduler/tasks/{name}/trigger` invoking `ScheduleRunner::runOne()`) routed through `SchedulerAdminApiRouter` and gated by `_role: admin` (NFR-001). `ScheduleRunner::runOne()` and a shared private `runTask()` helper back both the cron sweep and the on-demand trigger; `ScheduleRunResult` gains optional `status` / `message` / `exceptionClass` fields so the controller surfaces failures structurally without ever serialising a `\Throwable` (FR-010). `SchedulerServiceProvider` now binds `ScheduleStateRepository` as a container singleton so the L4 API provider can resolve it. New `/scheduler` Nuxt page mirrors `/queue` shape; `useScheduledTasks` composable; `SchedulerTaskRow` component with confirm-modal flow; "Operations" sidebar gains a `/scheduler` link. Tasks themselves remain code-defined via attributes — no edit UI (C-002). M4B WP02 of mission `queue-scheduler-admin-01KSBKQF`, closes M4B. (#1471)
- **Admin SPA: failed-jobs queue dashboard** at `/queue` with retry and discard. New `Waaseyaa\Api\Controller\QueueController` (paginated `GET /api/queue/jobs`, `POST /api/queue/jobs/{id}/retry`, `POST /api/queue/jobs/{id}/discard`) routed through `QueueAdminApiRouter` and gated by `_role: admin` (NFR-001). Retry re-enqueues via `QueueInterface::dispatch()` since `FailedJobRepositoryInterface::retry()` only returns + removes the record. New `/queue` Nuxt page mirrors `/workflows` shape; `useQueueJobs` composable; `QueueJobRow` + `QueuePayloadModal` components; "Operations" sidebar nav section. Queued / in-flight jobs are out of scope until `TransportInterface::listJobs()` lands (follow-up issue tracked under #1471). M4B WP01 of mission `queue-scheduler-admin-01KSBKQF`. (#1471)
- **agent-output**: `tests/Integration/PhaseN/AgentOutput/TokenReductionSmokeTest` empirically verifies the headline NFR-004 / SC-001 claim (≥90% character reduction). Standard PHPUnit output for `packages/foundation/tests/Unit --no-coverage` measures 2,209 bytes; the agent-output NDJSON envelope is 117 bytes — **94.70% reduction**. The smoke test pins this fixture and asserts `>= 0.90` via `proc_open` + envelope extraction. Verification log at `kitty-specs/agent-output-package-01KS5VX1/verification.md` (gate sweep, mission PR provenance #1559–#1570, first-release checklist status). M4 WP06 of mission `agent-output-package-01KS5VX1` — closes the mission.
- **release pipeline**: `.github/workflows/split.yml` now includes `packages/agent-output` in the per-package subtree-split matrix (Layer 0 / Foundation tier). After the next release-cut tag pushes, consumers will be able to `composer require waaseyaa/agent-output` once the manual `gh repo create waaseyaa/agent-output --public` + Packagist registration steps land per the package's new README "First-release checklist" section. M4 WP05 of mission `agent-output-package-01KS5VX1`.

- **agent-output**: `Waaseyaa\AgentOutput\Listener\AgentOutputPhpUnitExtension` — PHPUnit 10 extension that emits a `PhpUnitFormatter` NDJSON envelope on its own line on stdout when `WAASEYAA_OUTPUT=json` is set. Registered via `<extensions>` in `phpunit.xml.dist`; the extension is a no-op when the env var is unset (zero overhead for human-mode test runs, C-002 preserved). The envelope reports `passed`/`failed`/`skipped` counters plus a `failures` list with the failing test id + thrown message (file/line capture is best-effort: PHPUnit's `Code\Test` interface doesn't expose a `file()` method on every subtype, so the field is empty when the test type isn't a regular test method). Subscribers cover `Test\Passed`, `Test\Failed`, `Test\Errored`, `Test\Skipped`, `Test\MarkedIncomplete`, and `TestRunner\ExecutionFinished` (which prints the envelope). After this commit, every M4-tracked CI gate plus PHPUnit supports the agent-output contract. M4 WP04D (T015 + T018) of mission `agent-output-package-01KS5VX1`.
- **tools/drift-detector.sh**: `--output=json` flag + `WAASEYAA_OUTPUT=json` env var produce an NDJSON envelope via `Waaseyaa\AgentOutput\Formatter\DriftDetectorFormatter`. The wrapper re-execs the script with the flag stripped + env unset, captures stdout, and pipes through `DriftDetectorFormatter::parseRawOutput()` + `format()`. Default human-readable output (the canonical `=== Drift Detector ===` preface + stale/OK breakdown) is unchanged.
- **bin/check-phpstan** (new wrapper script): runs `vendor/bin/phpstan analyse --memory-limit=512M` with the same arg set as `composer phpstan`; adds the agent-output `--output=json` flag + `WAASEYAA_OUTPUT=json` env var that re-run phpstan with `--error-format=json` and translate the file/messages tree via `Waaseyaa\AgentOutput\Formatter\PhpStanFormatter`. `composer phpstan` remains the canonical human-mode entry point; this wrapper is the JSON-mode entry point so the composer script alias doesn't need an inner flag parser. M4 WP04C of mission `agent-output-package-01KS5VX1`.

- **bin/check-dead-code / bin/check-getquery-bindings / bin/check-composer-policy**: `--output=json` flag + `WAASEYAA_OUTPUT=json` env var produce NDJSON envelopes on stdout via the matching `Waaseyaa\AgentOutput\Formatter\*` class (`DeadCodeFormatter`, `GetQueryBindingsFormatter`, `ComposerPolicyFormatter`). Wrapper shape varies by script: `check-dead-code` (pure bash) re-runs phpstan with `--error-format=json` and translates the file/messages tree; `check-getquery-bindings` (pure PHP) adds `--output=json` parsing inside the script and emits the envelope directly via the formatter; `check-composer-policy` (bash + Python) mirrors the `check-package-layers` template — Python emits structured failures to a tmpfile, PHP shim feeds the formatter. Default (no flag, no env var) behaviour is unchanged for all three: human pass/fail text, exit 0/1. M4 WP04B of mission `agent-output-package-01KS5VX1`. The four `bin/check-*` scripts now uniformly support the agent-output contract; remaining WP04 items (PHPUnit event subscriber, PHPStan custom error formatter, `tools/drift-detector.sh` shim, PhpUnitJsonOutputTest) are still tracked under the original WP04 task file's deferred subtasks.
- **bin/check-package-layers**: new `--output=json` flag + `WAASEYAA_OUTPUT=json` env var produce an NDJSON envelope on stdout via `Waaseyaa\AgentOutput\Formatter\PackageLayersFormatter` instead of the human-readable pass/fail line. Envelope shape: `{tool: "check-package-layers", result: "pass"|"fail", packages_scanned: int, violations: [{source, target, edge}, ...]}`. Bash detects mode → Python emits structured findings to a tmpfile → PHP shim feeds the formatter. Default (no flag, no env var) behaviour is unchanged: human pass/fail line, exit 0/1. Pattern-proving slice of M4 WP04; the same wrapper template applies to the other three `bin/check-*` scripts in WP04B. M4 WP04 (part 1) of mission `agent-output-package-01KS5VX1`.

- **foundation: the kernel's migration-provider injection now resolves to a capability interface.** `AbstractKernel::injectMigrationProviders()` invoked `$provider->withMigrationProviders()` after an `instanceof` guard against the concrete migration `ServiceProvider` FQCN, which the `ServiceProviderContractTest` flags (kernel call sites must resolve to a named interface, not a concrete class). New Foundation capability interface `AcceptsMigrationProvidersInterface` declares `withMigrationProviders(list<object>): void`; the migration `ServiceProvider` implements it (filtering to migration providers), the kernel guards the call site with the interface, and the contract test's `CAPABILITY_INTERFACES` maps the method to it. Behaviour is unchanged; the `everyKernelCallSiteResolvesToInterfaceOrAbstractBase` contract is green again.
- **admin: RichText widget no longer scrambles typed input.** The contenteditable was bound via a reactive `v-html`, so every keystroke re-rendered the element and reset the caret to the start, reversing typed characters. It is now driven imperatively (innerHTML set on mount and on external value changes only, skipping the component's own input echo), preserving the caret during typing.
- **docs: public surface map records every public API element again.** `PublicSurfaceVerificationTest` (the surface-parity gate) flagged 19 `@api` interfaces/enums in `packages/*/src` with no disposition in `docs/public-surface-map.php` — the migration-provider capability interface added in this PR plus 18 elements from recent merges that landed without a map entry (audit query/writer contracts + event-kind enum, classification clearance/registry/parent-resolver ports, the API audit/mcp-admin/media-version/mercure-monitor read models, the MCP recent-invocations query port, the OIDC key-material provider, the migration content-model derivation port, and the page-builder decoder + discovery ports). All 19 are genuine public extension points carrying class-level `@api`, so each is mapped `public`; the three surface-map verification cases pass again. (PR #1614)

- **waaseyaa/messaging graduates from L2 (Content Types) to L3 (Services).** `bin/check-package-layers` updated (`"messaging": 3`). `CLAUDE.md` layer table updated: L2 row no longer contains `messaging`; L3 row appends `messaging`. `packages/messaging/README.md` updated to `**Layer 3 — Services**` with a "Why L3" rationale paragraph. New `docs/specs/messaging.md` documents the L3 graduation rationale, data model, access policy model, and out-of-scope follow-ups (presence, read-receipt UI, federation, Anokii Chat surface). The graduation aligns messaging with its chat-substrate role (gap-matrix C-1) and unblocks a future Anokii Chat surface mission. Mission `l2-content-types-consolidation-01KSEFTX` WP03.

- Declared JSON:API the framework's primary API surface; demoted `waaseyaa/graphql` from `waaseyaa/full` `require` to `suggest`. GraphQL remains supported; it is no longer in the recommended bundle. Distributions that want GraphQL: `composer require waaseyaa/graphql`. See `docs/specs/jsonapi.md` for the JSON:API ↔ GraphQL parity matrix and any follow-up missions for JSON:API gap-fills.
- Demoted `waaseyaa/inertia` from `waaseyaa/full` `require` to `suggest` (DIR-007 ratification). The package remains supported; it is no longer in the recommended bundle. Distributions that want Inertia: `composer require waaseyaa/inertia`.
- **docs**: M5 WP05 close-out — `docs/specs/bimaaji-install.md` filled in (Supported clients table with the seven launch-client target paths + convention citations, Flag semantics table with exit-code matrix, Interactive UX section documenting the shipped `CliIO::ask()` + `confirm()` prompts as the WP01 scaffold's reduced-scope replacement for the original `[o]verwrite/[s]kip/[d]iff/[a]ll` plan, Trust contract details, Implementation Status with PR provenance). `packages/bimaaji/README.md` adds an "Installing guidelines / skills" section with an invocation matrix and updates the Status section: M3's MCP bridge has shipped, so the README no longer describes bimaaji as PHP-only and the consumer-cleanup notes now point at the new `/mcp` HTTP endpoint. Verification artifact at `kitty-specs/bimaaji-install-command-01KS5W0S/verification.md`. M5 WP05 of mission `bimaaji-install-command-01KS5W0S`.

- **audit**: OCAP audit log now persists non-entity events and no longer recurses on itself. `EntityLifecycleAuditListener` skips `AuditEvent` saves/deletes (auditing an `AuditEvent` re-entered the writer → `save()` → `POST_SAVE` in unbounded recursion). `AuditEventWriter` coalesces a null `entity_type_id`/`entity_uuid` to `''` to match the table's `NOT NULL DEFAULT ''` columns — previously every non-entity event (`entity.read` on a path, `access.denied`, `agent.tool.execute`) hit a NOT NULL violation that the best-effort writer swallowed, so the events were silently dropped. Restores `GET /api/audit/events` results for non-entity event kinds. (#1587)
- **admin (MCP pages)**: the MCP tool registry and tool-detail pages now render their tables. The pages referenced auto-imported components by unprefixed names (`<ToolRegistryTable>`, `<InputSchemaViewer>`, `<RecentInvocationsTable>`); Nuxt registers nested components with the directory prefix, so these resolved to inert custom elements and never rendered when data was present. Renamed to `<McpToolRegistryTable>`/`<McpInputSchemaViewer>`/`<McpRecentInvocationsTable>`. (#1592)
- **bimaaji**: `bimaaji:install` sandbox-discipline check no longer rejects healthy target paths when the project root lives in a deep directory tree (e.g. `/home/<user>/.../<tempdir>`). The pre-fix walk-the-ancestor-chain implementation would reject on the first ancestor (`/home`) that wasn't a prefix of the project root. The new check only realpath-validates the nearest existing ancestor (catching symlink-based escapes) and falls back to textual `..`/absolute-path guarding for the remainder. M5 WP04 of mission `bimaaji-install-command-01KS5W0S`.
- **bimaaji**: `bimaaji:install` now exits non-zero when any required write was skipped due to a pre-existing diverging file plus `--force` absent on non-interactive stdin (previously exited 0 with `0 written, 0 unchanged, 1 skipped` summary only). Callers can now distinguish "everything fine" from "something was skipped that needs your attention" by exit code alone. Per-client `errors` counter is propagated to the overall exit code. M5 WP04 of mission `bimaaji-install-command-01KS5W0S`.

- **bimaaji**: Integration test surface for `bimaaji:install` under `tests/Integration/PhaseN/BimaajiInstall/` — five test files / 12 tests / 54 assertions covering: positive install + idempotent re-run, `--dry-run` flag, hand-edit preservation when `--force` is absent on non-interactive stdin, sandbox rejection of absolute paths and `..` traversals (via three controlled stub-transformer escape vectors), and the Levenshtein suggestion + far-typo fallback for unknown clients. Shared scaffolding in `BimaajiInstallTestCase` keeps the per-test boilerplate to a minimum (temp project root + chdir, seed-skill helpers, custom transformer injection through the constructor). M5 WP04 of mission `bimaaji-install-command-01KS5W0S`.

- **bimaaji**: `bin/waaseyaa bimaaji:install` CLI command — installs Waaseyaa framework guidelines + skills into a consuming project for one or more agent clients. Flags: `--client=<id>` (repeatable / comma-separated; available: claude, cursor, codex, copilot, gemini, windsurf, junie; prompts interactively when omitted on a TTY), `--features=<csv>` (default `guidelines,skills`; reserved for future filtering), `--dry-run` (preview the would-be write set without touching the filesystem), `--force` (skip every confirmation prompt and overwrite existing files unconditionally). Per-client transformer registry is composed in `BimaajiServiceProvider::defaultClientTransformers()` and handed to the command at construction. Idempotency: identical existing content is sha1-compared and counted as `unchanged` in the per-client summary. Sandbox: every target path resolves under the project root before any write happens (rejects absolute paths + `..` traversals). Unknown-client errors surface a Levenshtein suggestion. Aborts non-interactively without `--client` (FR-008/NFR-004). M5 WP03 of mission `bimaaji-install-command-01KS5W0S`.
- **bimaaji**: `Waaseyaa\Bimaaji\Install\SkillSetParser` — tiny YAML-frontmatter parser for `skills/waaseyaa/<id>/SKILL.md` files. Returns a sorted list of `ParsedSkill` value objects. Bimaaji does not depend on `symfony/yaml`; the parser handles the shape the framework's `SKILL.md` files actually produce (`key: value` pairs on single lines, with quote stripping, scalar coercion for true/false/int/null, and continuation-line support). Bound as a container singleton; skills directory resolution prefers `config['bimaaji']['skills_directory']` and falls back to `<projectRoot>/skills/waaseyaa`. M5 WP03 of mission `bimaaji-install-command-01KS5W0S`.

- **docs**: M3 WP04 doctrine spec edits. `docs/specs/mcp-endpoint.md` gains a "Bimaaji MCP bridge" section documenting the shipped surface (five `#[AsAgentTool]` adapters, account-permission capability model, HTTP Streamable transport via `/mcp`, per-request bridge architecture, disk-write invariant, M-G → M3 transition rationale) plus a supersession callout above the 2026-05-20 "Bimaaji MCP positioning (PHP-only)" section (preserved verbatim per C-005). `docs/specs/bimaaji.md` adds an "MCP exposure" subsection enumerating the five tools and flips M2 + M3 from Deferred → Shipped in the Implementation Status section. `packages/mcp/README.md` rewritten from a 9-line stub to ~90 lines covering the bridge architecture, the bimaaji tool family, a `claude_desktop_config.json` example, and the canonical-spec pointer. Replaces the stale `EditorialTools` key-classes reference. M3 WP04 of mission `bimaaji-mcp-bridge-01KS5VS8`.

- **mcp**: `McpEndpoint::__construct` now takes `(McpAuthInterface, Waaseyaa\AI\Tools\ToolRegistryInterface)` instead of `(McpAuthInterface, Mcp\Bridge\ToolRegistryInterface, Mcp\Bridge\ToolExecutorInterface)`. `dispatch()` constructs a per-request `AgentToolRegistryBridge` with the account `McpAuthInterface::authenticate()` resolved from the Authorization header, so per-tool capability gating (`AbstractAgentTool::requireCapability`) runs against the correct identity. Closes the WP02 caveat (placeholder no-permission account at boot leaked into every `tools/call`). `Mcp\Bridge\ToolRegistryInterface` and `Mcp\Bridge\ToolExecutorInterface` remain `@api` as the bridge's implemented contracts but are no longer container-bound. `BimaajiMcpReadTest` now asserts the closed-loop semantics (read account → success envelope); `BimaajiMcpCapabilityTest` adds end-to-end coverage of the forbidden envelope for missing capabilities. M3 WP03 of mission `bimaaji-mcp-bridge-01KS5VS8`.

- **mcp**: `McpServiceProvider::register()` binds `Mcp\Auth\McpAuthInterface` to the empty-token `BearerTokenAuth` default. Production deployments override via the kernel-services bus or by re-binding the abstract in an application provider. The bridge architecture is per-request inside `McpEndpoint::dispatch()` — see Changed entry above. M3 WP02+WP03 of mission `bimaaji-mcp-bridge-01KS5VS8`.
- **ai-agent**: `Waaseyaa\AI\Agent\Tool\Bimaaji\SearchSpecsTool` — the fifth bimaaji `#[AsAgentTool]` adapter, capability `bimaaji.read`. Substring-matches a query against the bodies of every markdown file enumerated by `Waaseyaa\Bimaaji\Spec\SpecIndexProvider` and returns `{file, section_title, line_number, snippet}` per match. `inputSchema` requires `query` (non-empty string) and accepts optional `limit` (1–100, default 20). Naïve case-insensitive substring search per AD-04 — for ranked or trigram-based results, see the mission plan follow-up notes. No filesystem writes; no recursive directory walk. M3 WP02 of mission `bimaaji-mcp-bridge-01KS5VS8`.
- **bimaaji**: `BimaajiServiceProvider` now binds `SpecIndexProvider` as a container-resolvable singleton. Spec directory resolution prefers `config['bimaaji']['specs_directory']` and falls back to `<projectRoot>/docs/specs`. M3 WP02 of mission `bimaaji-mcp-bridge-01KS5VS8`.
- **bimaaji**: `Waaseyaa\Bimaaji\Install\ClientTransformerInterface` + 7 concrete client transformers — `ClaudeClientTransformer` (multi-file: one `.claude/skills/waaseyaa-<id>.md` per skill plus a shared `.claude/CLAUDE-WAASEYAA.md` index) and six single-file transformers sharing `AbstractSingleFileClientTransformer` (Cursor → `.cursorrules`, Codex → `.codex/AGENTS.md`, Copilot → `.github/copilot-instructions.md`, Gemini → `GEMINI.md`, Windsurf → `.windsurfrules`, Junie → `.junie/guidelines.md`). The single-file base wraps the standardised prelude + skill bodies in `<!-- waaseyaa:bimaaji:install BEGIN -->` / `END` markers so the M5 WP03 install command can introduce a `--merge` mode without clobbering hand-written content above or below. Two plain-value DTOs — `ParsedSkill` (id, name, description, frontmatter, body) and `TargetFile` (path, content, sourceSkill) — pin the transformer contract; per-client unit tests use the fixture set at `packages/bimaaji/tests/Fixture/Skills/` so they don't depend on the live `skills/waaseyaa/*` content. Each transformer's class-level docblock cites the upstream convention URL + verification date so convention drift is caught at the M5 WP05 smoke. M5 WP02 of mission `bimaaji-install-command-01KS5W0S`.
- **agent-output**: Seven new `FormatterInterface` implementations covering the remaining first-party CI gates and test runners — `PestFormatter` (tool `pest`), `PhpStanFormatter` (tool `phpstan`), `PackageLayersFormatter` (tool `check-package-layers`), `DeadCodeFormatter` (tool `check-dead-code`, baseline-aware), `GetQueryBindingsFormatter` (tool `check-getquery-bindings`, baseline-aware), `ComposerPolicyFormatter` (tool `check-composer-policy`), and `DriftDetectorFormatter` (tool `drift-detector`, ships with a `parseRawOutput()` adapter that turns the shell script's stdout into the structured event the `format()` method expects). Each formatter has a contract test covering pass / fail / empty / single-line NDJSON / NFR-003 envelope-size, with the baseline-aware ones additionally pinning the "baseline findings stay silent, only new offenders flip `result: fail`" rule. M4 WP03 of mission `agent-output-package-01KS5VX1`; CLI wiring follows in WP04.
- **docs**: M2 (`ai-agent-bimaaji-tools-01KS5VKR`) close-out — `packages/ai-agent/README.md` gains a "Bimaaji-backed tools" section enumerating the four `#[AsAgentTool]` adapters (`bimaaji_introspect_graph`, `bimaaji_introspect_section`, `bimaaji_propose_mutation`, `bimaaji_generate_patch`), the two new capability strings (`bimaaji.read` generally safe; `bimaaji.mutate` default-off per AD-02), a `bin/waaseyaa ai:run` example wiring the demo agent, and pointers to the M2 mission spec and the SC-004 tool-shape contract. The contract itself lives in `kitty-specs/ai-agent-bimaaji-tools-01KS5VKR/verification.md` — for each of the four tools it records FQCN, `#[AsAgentTool]` parameters, argument schema, success/failure envelope shapes (including the explicit no-filesystem-write invariant for `GeneratePatchTool`), and the contract test pin. M3 (`bimaaji-mcp-bridge-01KS5VS8`) imports this surface map verbatim when wrapping the four tools as MCP tools. M2 WP05 of mission `ai-agent-bimaaji-tools-01KS5VKR`.
- **ai-agent**: `Waaseyaa\AI\Agent\Tests\Fixture\BimaajiDemoAgent` reference `#[AsAgentDefinition]` plus two end-to-end integration tests under `tests/Integration/PhaseN/AgentRuntime/`: `BimaajiAgentRunTest` (positive path — drives the introspect → propose → generate sequence through `AgentExecutor` against an in-memory SQLite kernel and asserts the run reaches `RunStatus::Completed`, the audit log records `tool_call` rows for all three bimaaji tool names, and `generate_patch`'s `tool_result` summary surfaces the `PatchSet:` count line) and `BimaajiAgentRunCapabilityTest` (negative path — same flow but with a read-only account; asserts introspect succeeds while both mutation tools surface `success=0` / `tool_result_summary='forbidden'` `error` rows in the audit log without crashing the run). Shared scaffolding lives in `tests/Integration/PhaseN/AgentRuntime/Fixture/BimaajiAgentRuntimeKernel` so the two tests reuse one boot path. M2 WP04 of mission `ai-agent-bimaaji-tools-01KS5VKR`.
- **ai-agent**: `Waaseyaa\AI\Agent\Tool\Bimaaji\ProposeMutationTool` + `GeneratePatchTool` — the two mutation-side `#[AsAgentTool]` adapters that let an embedded agent propose a validated schema mutation and emit the resulting `PatchSet`. Both gated on `bimaaji.mutate` (default off per AD-02); both `destructive: false` because `GeneratePatchTool` builds patch content + diffText in-memory and never touches the filesystem — the SC-005 invariant is pinned by `GeneratePatchToolTest::doesNotWriteToFilesystem`, which snapshots a controlled temp dir and asserts the advertised patch path does not exist on disk after `execute()`. `GeneratePatchTool` re-validates internally (a JSON tool call cannot carry a pre-validated `MutationResult` and trusting client-supplied validation state would defeat the validator's purpose). Microbenchmark (n=50, median): tool overhead 0.8 μs above `MutationValidator::validate()` direct and 0.9 μs above `PatchGenerator::generate()` direct — five orders of magnitude under the NFR-002 50 ms budget. M2 WP03 of mission `ai-agent-bimaaji-tools-01KS5VKR`.
- **agent-output**: `Waaseyaa\AgentOutput\FormatterInterface` plus `Waaseyaa\AgentOutput\Formatter\PhpUnitFormatter` — the contract every JSON envelope formatter implements, and the reference implementation that wraps PHPUnit events into a compact NDJSON envelope (`{tool, result, suite, passed, failed, skipped, duration_ms, failures}`). Schema documented in `docs/specs/agent-output.md`. M4 WP02 of mission `agent-output-package-01KS5VX1`; seven more first-party formatters (Pest, PHPStan, our four `check-*` gates, drift-detector) follow in WP03, CLI wiring in WP04.
- **ai-agent**: `Waaseyaa\AI\Agent\Tool\Bimaaji\IntrospectGraphTool` + `IntrospectSectionTool` — two `#[AsAgentTool]` adapters that let an embedded agent introspect a running application via M1's `ApplicationGraphGenerator`. Both gated on the `bimaaji.read` capability; non-destructive; dry-run-safe. Section tool advertises a JSON-schema enum of the six default section keys and emits a structured `Unknown section` error envelope listing the available keys when a caller misses. M2 WP02 of mission `ai-agent-bimaaji-tools-01KS5VKR`.
- **agent-output (new package)**: `waaseyaa/agent-output` Layer-0 package — PAO-equivalent agent-runtime detection plus a forthcoming JSON-formatter contract. M4 WP01 lands `Waaseyaa\AgentOutput\AgentDetector` covering seven launch clients (Claude Code, Cursor, Codex, Gemini CLI, Windsurf, Junie, GitHub Copilot) via env-var lookup; envelope formatters and CLI integration follow in M4 WP02–WP06. See `kitty-specs/agent-output-package-01KS5VX1/plan.md`.
- **bimaaji**: `BimaajiServiceProvider` now binds `MutationValidator`, `PatchGenerator`, and `SovereigntyGuardrails` as container-resolvable singletons. M1 left these container-invisible because no consumer needed them; M2 WP01 (`ai-agent-bimaaji-tools-01KS5VKR`) wires them up under the documented C-002 exception so M2 WP03's mutation-tool adapters can resolve them without further bimaaji surgery.
- **bimaaji**: `bin/waaseyaa graph:dump` CLI command emits the application graph as JSON. Three flags: `--section=<key>` scopes output to a single section (admin/entities/jsonapi/public_surface/routing/sovereignty), `--strict` fails closed on provider errors with the throwable class name in the stderr message, and `--format` is reserved for a future yaml backend (only `json` accepted in beta). Output is byte-for-byte stable across runs for MCP consumers that diff snapshots. Closes M1 WP02 of mission `bimaaji-wakeup-01KS5VEY` (see `docs/plans/2026-05-21-ai-ecosystem-beta-tightening.md`).

- **foundation**: `Waaseyaa\Foundation\Http\Router\McpRouter` deleted along with its entry in `HttpKernel::$foundationRouters` (line 411) and `McpRouterTest`. The router was dead in production — it guarded a literal `_controller === 'mcp.endpoint'` string that no real route ever set (only artificial unit-test fixtures did); `McpRouteProvider` registers `/mcp` with `_controller = 'Waaseyaa\\Mcp\\McpEndpoint::handle'`, so `/mcp` dispatch already flowed through SSR's `AppControllerRouter` to the new `McpEndpoint`. Retiring the router unblocks M3's bridge-architecture work and removes the last competing intercept for `/mcp`. Legacy `Waaseyaa\Mcp\McpController` + its `Tools/` / `Cache/` / `Rpc/` siblings remain in-place (still test-covered via direct instantiation in `tests/Integration/Phase14/AiMcpIntegrationTest.php` and the package's own unit tests) but are no longer reachable from HTTP routing — a future cleanup mission may retire them. M3 WP01 of mission `bimaaji-mcp-bridge-01KS5VS8`.

- **ci**: `packages/ai-agent/tests/Contract` is now registered in the Unit testsuite in `phpunit.xml.dist`. Without this, the WP02 read-tool contract tests (`IntrospectGraphToolTest`, `IntrospectSectionToolTest`, `BimaajiToolCapabilityTest`) and any future ai-agent contract tests were silently not discovered by `composer test`. Caught when WP03's contract tests failed to run via `--filter`; fixed in the same PR so all 29 ai-agent contract tests now flow through CI.
Assets 2
Loading