Task Breakdown: Governance Policy Matrices (Merge Policy + Tool Grants)
Companion to
./spec.mdand./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_PRECEDENCEandMERGE_POLICY_FIELDS. (FR-9, FR-10, FR-1) - T3 Ship
sanitizeMergePolicyOverrideimplementing 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
sanitizeToolGrantOverridewith the same posture as T3. (FR-5) - T7 Ship the pattern matchers
matchesToolPattern,matchesAnyToolPatternandtoolPatternCovers, all case-insensitive. (FR-19, FR-20) - T8 Ship
TOOL_NAME_PATTERN,TOOL_GRANT_PATTERN,CREDENTIAL_KEY_PATTERN,isCredentialKeyand thecredentialRefPattern()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— intersectallow, uniondeny, 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
partitionToolsByGrantso 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_ENFORCERandTOOL_GRANT_ENFORCERas leaf files with type-only imports; bound byPolicyModule. - T20 Consume via
@Optional() @Inject(...)inGitFacadeService(merge policy) andAgentRunService/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
PullRequestGateServiceconsumes the merge decision. (FR-11)
Phase 4 — Migrations
- T23
AddMergePolicyColumns— additivesimple-jsoncolumns 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/capabilitiesreflects 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
denytoo, 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-onlymatrix mode that reports refusals without enforcing them, to de-risk a first rollout. - T39 (OQ-5) Deep-freeze
PLATFORM_DEFAULT_MERGE_POLICY'sallowedMergeMethods/protectedBranchesarrays so they matchPLATFORM_DEFAULT_TOOL_GRANTand a stray.push()throws instead of silently widening a safety control (spec E10). - T40 (OQ-6) Decide whether
protectedBranchesshould 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
isCredentialKeyto reject non-string input. Shipped 2026-08-23: the signature is now(value: unknown): value is stringwith an explicittypeofguard, 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.