14 KiB
issue, issue_title
| issue | issue_title |
|---|---|
| 581 | pi-permission-system: decision record for the case-by-case model judge (ModelTriageAuthorizer) |
ADR 0007 — decision record for the case-by-case model judge (ModelTriageAuthorizer)
Release Recommendation
Release: ship independently
Phase 11 Step 7 is tagged Release: independent in the roadmap, and it is not a member of any release batch (the only Phase 11 batch is "shell-tool-aliases": Steps 2, 3).
This is a documentation-only step — a docs: ADR plus a roadmap-completion doc update — so it carries no code change and gates nothing.
Problem Statement
Issue #472 (a case-by-case model judge beyond yoloMode) has been deferred by name in Phases 9 and 10.
The repeat-deferral rule requires an explicit decision this phase rather than a third silent re-defer.
The ModelTriageAuthorizer design is already settled in the architecture doc's Discriminating delegation section; what stands between "designed" and "schedulable" is committing the open parameters to a decision record.
This is documentation only. It does not implement the judge — #472 stays open, tracking the implementation, and gains a linked ADR.
Goals
- Write
docs/decisions/0007-model-triage-authorizer.mdrecording the settled design parameters ofModelTriageAuthorizer:- Decision surface — ask-only (the judge sees
ask, neverallow/deny), preserving the deny-preserving boundary. - Shape — a decorator,
ModelTriageAuthorizer(inner), not a fourth channel. - Failure behavior — fail-closed delegation to
inneron model-unreachable / timeout / low-confidence, never an auto-allow. - Audit tagging — a model grant is distinguished in the review log as
origin: "authorizer:model"(with model version and structured intent), mirroring how yolo grants carryorigin: "yolo". - Non-persistence — a model verdict stays live-only; it does not silently become recorded authority.
- Bounded delegation — which surfaces the model may auto-allow is itself ruleset-expressible, with
external_directoryand secret-shapedpathrules excluded so they always reach the human.
- Decision surface — ask-only (the judge sees
- Mark Phase 11 Step 7 complete in
docs/architecture/architecture.md(step heading✅+ Mermaid node✅), and link the new ADR from theDiscriminating delegationsection. - Leave #472 open with a comment linking the ADR, so it becomes schedulable in a future phase on its own merits.
This change is not breaking — it adds a decision record and a doc-completion marker; no config, schema, behavior, or default changes.
Non-Goals
- Implementing
ModelTriageAuthorizer— that is #472's scope, gated on this ADR. Nosrc/change, noRuleOriginenum extension, no config field, no schema regeneration. - Deciding the model provider, prompt, confidence-threshold value, or timeout duration — those are implementation parameters for #472, deliberately left to that issue.
- The non-deterministic access-intent classifier (a model shaping the intent before
evaluate()) — a distinct, more distant direction the architecture doc already flags as warranting its own future ADR. - Multi-hop escalation, grant-scope selection, and yolo inheritance — already settled in ADRs 0005 / 0006 and the architecture's
Resolved directionsection; the ADR references them but does not re-decide them.
Background
Relevant existing surfaces:
src/authority/authorizer.ts— theAuthorizerinterface (authorize(details): Promise<PermissionPromptDecision>) andselectAuthorizer, which picksLocalUserAuthorizer/ParentAuthorizer/DenyingAuthorizeronce per session activation. The ADR's decorator wraps whichever of these is selected — this is the interface the futureModelTriageAuthorizer(inner)implements and composes over.src/rule.ts—RuleOrigin(currentlyglobal | project | agent | project-agent | builtin | baseline | session | yolo) andrewriteAsksToYolo, the composition-stageask→allowrewrite taggedorigin: "yolo". The audit-tagging decision names"authorizer:model"as the model-grant analogue; whether that lands as aRuleOriginmember or a separate review-log field is an implementation detail deferred to #472 (a model grant is non-persistent, so it does not necessarily become aRule).- ADR 0005 (
docs/decisions/0005-serving-authorizer-provenance.md) — establishes that anAuthorizeris live authority and never touchesevaluate(); the non-determinism principle governs recorded authority only. This is the precedent the model-judge ADR extends:LocalUserAuthorizeris already a non-deterministic oracle (the human), so a model holding the same role is consistent with the existing model.
Constraints from AGENTS.md and the package skill that apply:
- ADR numbering is per-package; next free is
0007(existing run0001–0006). docs/architecture/architecture.mdis shipped in the npm tarball allowlist and inline-copies corerule.tstypes — but this ADR adds norule.tsfield, so that listing needs no edit.- Mark the roadmap step complete in the implementation doc-update commit (this build), not a deferred
/ship-issuecommit —✅on both the step heading and its Mermaid node. - Reference GitHub issues in long-lived docs with reference-style links (
[#N]+ a file-scoped[#N]:definition).
Design Overview
This is a documentation change; the "design" is the ADR's decision content and its faithfulness to the settled architecture-doc section.
ADR structure
Follow the established ADR template (0005 / 0006): YAML frontmatter (status: accepted, date), # 0007 — <title>, then ## Status, ## Context, ## Decision, ## Consequences (with ### Accepted limitations), and reference-link definitions.
The ## Decision section records six settled parameters as numbered decisions:
-
Ask-only decision surface. The judge sees only
ask. Denies are decided by recorded authority (evaluate()) and structurally never reach anAuthorizer, so the model cannot grant a hard deny — the safeguard for a sensitive resource stays an explicitdenyrule, which survives the model exactly as it survives the yolo rewrite. Where yolo rewrites everyasktoallow, the model resolves only the asks it is confident about and escalates the rest: a discriminating, deny-preserving yolo, a middle rung between prompt-everything and allow-everything. -
Decorator shape, not a fourth channel.
ModelTriageAuthorizer(inner)wraps whicheverAuthorizerselectAuthorizerproduced (LocalUser/Parent/Denying) and implements the same one-method interface. It is the recursion "a node'sAuthorizeris its own parent" with the model's parent beinginner. Rejected alternative: a distinct fourth selection channel alongside the three-way dispatch — rejected because it duplicates the escalation wiring the decorator gets for free. -
Fail-closed delegation. Model unreachable, timeout, or low confidence delegates to
inner(the human,ParentAuthorizer, orDenyingAuthorizer), never an auto-allow. Under a headlessDenyingAuthorizerinner, an uncertain model verdict therefore denies — the fail-safe direction. -
Audit tagging. A model grant is distinguished in the review log as
origin: "authorizer:model", carrying the model version and the structured intent, mirroringorigin: "yolo". Note the implementation seam (deferred to #472): a model grant is non-persistent, so unlike yolo it does not become aRulein the ruleset — the tag rides the review-log entry / decision source, not necessarily theRuleOriginenum. The ADR settles the decision (model grants are audited and distinguishable); the mechanism is #472's. -
Non-persistence — live-only. A model verdict stays live-only; it does not silently become recorded authority. Unlike a human's "for this session" ruling, a probabilistic judgment never hardens into durable config. Rejected alternative: persist model grants quarantined for later human review. Rejected for this ADR as added machinery with no present consumer — live-only is the simpler fail-safe default; a quarantine store can be a named future extension if a review workflow ever wants it.
-
Bounded delegation, ruleset-expressible. Which surfaces the model may auto-allow is itself expressed as ruleset config, with
external_directoryand secret-shapedpathrules excluded so they always reach the human. This keeps the delegation boundary reviewable in the same config the rest of the policy lives in, honoring the package principle "prefer config patterns over new runtime mechanisms."
Relationship to evaluate() and #509
The ADR situates the model judge as the ask-consuming side of the boundary, distinct from the ask-producing side (evaluate() and rule-driven promotion, #509).
A promoted bare filename (git grep id_rsa prompts) is a deliberate fail-safe false positive on the producing side; ModelTriageAuthorizer dismisses such a false positive on the consuming side without hard-coding per-command file-argument tables.
The ADR notes this is the principled successor to the per-command argument-position work deferred from #509, and that the two compose cleanly because a promoted token emits the same structured descriptor a prefixed path does — the Authorizer needs no promotion-specific knowledge.
No new collaborator to sketch
The ADR introduces no code and no runtime collaborator in this change.
The decorator's call site is the existing selectAuthorizer return value wrapped as new ModelTriageAuthorizer(selected), exercising the already-shipped one-method Authorizer interface — a Tell-Don't-Ask shape (authorize(details) returns a decision; the caller does not inspect the authorizer's state).
The concrete wiring is #472's to build and test.
Module-Level Changes
Documentation only.
- New:
packages/pi-permission-system/docs/decisions/0007-model-triage-authorizer.md— the ADR described in Design Overview. - Changed:
packages/pi-permission-system/docs/architecture/architecture.md:- Mark Phase 11 Step 7 complete:
✅on the#### Step 7:heading and on theS7[...]Mermaid node in the step-dependency diagram. - Add a link to the new ADR from the
### Discriminating delegation: a model Authorizersection (a "Decision recorded in ADR 0007 (docs/decisions/0007-model-triage-authorizer.md)" note), and update the #472/Step-7 deferral references so they point at the recorded decision rather than an open deferral. - No edit to the inline
rule.tstype listing — this change adds noRuleOriginmember.
- Mark Phase 11 Step 7 complete:
- No change to
src/,test/,schemas/,config/,README.md,docs/configuration.md, or the package allowlist — the ADR ships via the already-listeddocs/decisionspath, and no user-facing config or command changes.
Grep confirmation performed during planning: ModelTriageAuthorizer appears only in docs/architecture/architecture.md (design narrative) — no src/ or test/ occurrence, no README/configuration mention — so no code or user-doc surface references the not-yet-built symbol.
Test Impact Analysis
None.
This is a documentation-only change with no test surface — it goes through /build-plan, not /tdd-plan.
The verification gate is pnpm exec rumdl check on the new and edited markdown plus the standard pnpm run lint / link-reference checks; the pre-completion-reviewer covers Mermaid-diagram validity for the edited node.
Invariants at risk
None.
No prior phase step's code invariant is touched — the change adds a decision record and a completion marker.
The one cross-doc invariant to preserve is internal consistency: the architecture doc's Discriminating delegation narrative and the new ADR must not disagree on any of the six parameters.
The build step verifies this by re-reading both after editing.
Build Order
This is a docs/config change (no red→green test cycles); execute as an ordered build.
- Write the ADR.
Create
docs/decisions/0007-model-triage-authorizer.mdper Design Overview. Lint withpnpm exec rumdl check <file>. Commit:docs(pi-permission-system): record ADR 0007 for the model triage authorizer (#581). - Update the architecture roadmap.
Mark Step 7
✅(heading + Mermaid node), link the ADR from theDiscriminating delegationsection, and refresh the #472 deferral references. Lint the file. Commit:docs(pi-permission-system): mark Phase 11 Step 7 complete and link ADR 0007 (#581). - Comment on #472. Post a comment linking the recorded ADR so the issue is schedulable on its own merits (done at ship time via the normal flow, or noted here for the ship step — no code change).
The two doc commits may be squashed into one if preferred; both are docs: (a hidden changelog type) and neither cuts a release on its own.
Risks and Mitigations
- Risk: the ADR drifts from the settled architecture-doc design.
Mitigation: the ADR is transcribed directly from the
Discriminating delegationsection; the build step re-reads both for consistency, and the pre-completion reviewer checks documentation coherence. - Risk: over-specifying implementation detail the ADR should leave to #472.
Mitigation: the ADR records decisions (surface, shape, failure mode, tagging, persistence, bounds), and explicitly defers mechanism (model choice, threshold values,
RuleOrigin-vs-log-field) to #472 in anAccepted limitations/ open-implementation note. - Risk: marking Step 7 complete implies the judge is built.
Mitigation: Step 7's
Outcomeis explicitly "carries a linked ADR and becomes schedulable … no code change"; the completion marker and the retained-open #472 together make the docs-only nature unambiguous.
Open Questions
- Audit-tag mechanism — whether
origin: "authorizer:model"lands as aRuleOriginenum member or a distinct review-log field is deferred to #472; the ADR settles only that model grants are audited and distinguishable. - Quarantine persistence — the ADR settles live-only and records quarantined-for-review as a rejected-for-now alternative; if a human-review workflow later wants it, that is a named future extension, not reopened here.