Skip to main content

Task Breakdown: Governance Policy Matrices (Merge Policy + Tool Grants)

Companion to ./spec.md and ./plan.md. Shipped tasks are recorded for traceability; outstanding work is tracked at the bottom and cross-referenced to the spec's open questions.

Feature ID: policy-matrices Status: Implemented (core), with follow-ups outstanding Last updated: 2026-08-22


How to use

Each task names the behaviour it delivers and the spec requirement it satisfies. A task is done when its code and its tests are merged (Constitution Principle VI — tests are a prerequisite, not a follow-up).

Phase 1 — Contracts (types, constants, guards)

  • T1 Define the merge-policy wire shape: MergePolicy, MergePolicyOverride, MergePolicyScope/MergePolicySource, MergePolicyChainEntry, ResolvedMergePolicy, MergeDecision. (FR-7)
  • T2 Ship the frozen conservative platform default plus MERGE_METHODS, MERGE_POLICY_SCOPE_PRECEDENCE and MERGE_POLICY_FIELDS. (FR-9, FR-10, FR-1)
  • T3 Ship sanitizeMergePolicyOverride implementing drop-if-unrecognised, trim + de-duplicate, and the empty-but-declared vs entirely-invalid split. (FR-5, E3, E4, E5)
  • T4 Define the tool-grant wire shape: ToolGrantOverride, ToolGrantMatrix, scope/source types, chain + decision types. (FR-15)
  • T5 Ship the frozen permissive platform default and TOOL_GRANT_SCOPE_PRECEDENCE. (FR-16, FR-1)
  • T6 Ship sanitizeToolGrantOverride with the same posture as T3. (FR-5)
  • T7 Ship the pattern matchers matchesToolPattern, matchesAnyToolPattern and toolPatternCovers, all case-insensitive. (FR-19, FR-20)
  • T8 Ship TOOL_NAME_PATTERN, TOOL_GRANT_PATTERN, CREDENTIAL_KEY_PATTERN, isCredentialKey and the credentialRefPattern() factory. (FR-24, FR-25, FR-26)

Phase 2 — Pure resolution & decision (agent package)

  • T9 resolveMergePolicyChain — sorts layers into precedence, folds field-by-field over a defensive copy of the platform default, and reports the owning scope of every field. (FR-2, FR-4, FR-6, FR-8)
  • T10 evaluateAgentMerge — the single decision point, with refusals ordered most- to least-fundamental, each carrying a stable code and a reason naming the offending value; never throws. (FR-11, FR-12, FR-14)
  • T11 Branch normalisation: strip refs/heads/, compare case-insensitively, fail closed on an unknown target while branches are protected. (FR-13, E1)
  • T12 Treat an omitted merge method as merge. (E2)
  • T13 narrowAllowPatterns — exported separately because the split is the no-upward-widening rule, and a rule that cannot be tested in isolation is a rule nobody trusts. (FR-17)
  • T14 resolveToolGrantChain — intersect allow, union deny, record rejected patterns per layer. (FR-17, FR-18, FR-4)
  • T15 decideToolGrant — deny before allow, distinct code for an empty/non-string tool name. (FR-21, FR-22, E8, E9)
  • T16 partitionToolsByGrant so no caller re-implements the ordering. (FR-23)

Phase 3 — Services, repositories, wiring

  • T17 MergePolicyService / ToolGrantService — the I/O half that loads the four scope rows and delegates to the pure functions.
  • T18 MergePolicyRepository / ToolGrantRepository.
  • T19 Injection tokens MERGE_POLICY_ENFORCER and TOOL_GRANT_ENFORCER as leaf files with type-only imports; bound by PolicyModule.
  • T20 Consume via @Optional() @Inject(...) in GitFacadeService (merge policy) and AgentRunService / AgentToolService / SkillsService (tool grants) so an unbound token restores pre-feature behaviour. (E7)
  • T21 credential-resolver / credential-interpolation — resolve {{cred.<key>}} server-side; never log, persist or echo the value. (FR-24)
  • T22 PullRequestGateService consumes the merge decision. (FR-11)

Phase 4 — Migrations

  • T23 AddMergePolicyColumns — additive simple-json columns on the four scope tables. (Principle V)
  • T24 CreateToolGrants — user-scoped grant table. (Principle V)

Phase 5 — API surface

  • T25 GET /api/merge-policy/resolve. (FR-27)
  • T26 GET /api/tool-grants/resolve, GET /api/tool-grants/check, GET /api/tool-grants, PUT /api/tool-grants, DELETE /api/tool-grants/:id. (FR-28)
  • T27 GET /api/agents/:id/capabilities reflects the resolved matrix.

Phase 6 — Tests

  • T28 Unit tests for the pure agent-package halves (packages/agent/src/policy/__tests__/).
  • T29 Controller tests for the API surface (merge-policy.controller.spec.ts, tool-grants.controller.spec.ts).
  • T30 Contracts-level unit tests pinning every exported constant list (members, count, order), both shape guards' empty-vs-invalid split, all three pattern matchers, the regex anchoring, and the credentialRefPattern() per-call-instance guarantee. (AC-11)

Phase 7 — Docs

  • T31 This Spec Kit feature (spec.md / plan.md / tasks.md) — the subsystem previously had only passing mentions in unrelated specs.

Outstanding follow-ups

  • T32 (OQ-1) Surface rejected allow patterns as an operator-visible warning, not only as a chain entry.
  • T33 (OQ-2) Report a narrowing result for deny too, for symmetry.
  • T34 (OQ-3) Validate merge-policy overrides at the API write boundary in addition to the read-time drop.
  • T35 (OQ-4) Add an audit-only matrix mode that reports refusals without enforcing them, to de-risk a first rollout.
  • T39 (OQ-5) Deep-freeze PLATFORM_DEFAULT_MERGE_POLICY's allowedMergeMethods / protectedBranches arrays so they match PLATFORM_DEFAULT_TOOL_GRANT and a stray .push() throws instead of silently widening a safety control (spec E10).
  • T40 (OQ-6) Decide whether protectedBranches should be case-normalised on write. Today dedup is case-sensitive at sanitize time while matching is case-insensitive at decision time, so ['main', 'MAIN'] stores two entries that behave as one.
  • T41 (OQ-7) Harden isCredentialKey to reject non-string input. Shipped 2026-08-23: the signature is now (value: unknown): value is string with an explicit typeof guard, and the spec that pinned the coercion hole was flipped into an 11-case regression matrix.
  • T36 Settings UI for editing both matrices (spec §6 defers this).
  • T37 Integration test against a real Postgres covering the full four-scope load path (today's coverage is unit-level plus controller-level).
  • T38 End-to-end test asserting an agent is actually blocked from merging into a protected branch through the real git facade.

Definition of Done

  • Behaviour matches ./spec.md §3 and satisfies §7's acceptance criteria.
  • Schema changes shipped as forward-only migrations.
  • Tests accompany the change and pin every constant, branch and refusal code.
  • No secret value is logged, persisted or returned to the model.