Files
my-pi/pi-permission-system/docs/retro/0595-adr-0008-cross-session-access-intent.md

127 lines
14 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
issue: 595
issue_title: "pi-permission-system: ADR 0008 — forwarded access-intent portability and principal identity"
---
# Retro: #595 — ADR 0008 forwarded access-intent portability and principal identity
## Stage: Planning (2026-07-17T23:56:20Z)
### Session summary
Planned Phase 12 Step 1 (Track A) — a docs-only ADR (`docs/decisions/0008-cross-session-access-intent.md`) that settles the cross-session access-intent contract before Steps 23 change the wire.
Because the deliverable is a decision, ran the `ask_user` gate over the three deliberative parameters and confirmed all three with the operator.
Wrote `packages/pi-permission-system/docs/plans/0595-adr-0008-cross-session-access-intent.md` and committed it.
### Observations
- **Three decisions confirmed with the operator** (two `ask_user` rounds — the first surfaced the parameters, the second tightened them after grounding in the code):
1. **Agent-scoped serving**`requesterAgentName` graduates from display-only to decision-participating; the parent resolves against its own ruleset scoped to the requester's agent name.
Clarified the operator's double-apply worry: forwarding up means the child landed on `ask` (unresolved), and the parent applies a *different* ruleset — a strict superset of agent-neutral, no double-application.
2. **Child-fixed match set** — ship `matchValues()` `boundaryValue()`; the parent never re-derives through its own `PathNormalizer`/cwd.
3. **Required field, floor to `ask` on absence**`ForwardedAccessIntent` is the sole resolution path (legacy `(surface, value)` branch retired in Step 3); a request missing it floors to `ask`, never a hard deny or silent grant.
- **Key grounding finding that resolved the operator's portability uncertainty**: `AccessPath.matchValues()` already carries a **cwd-relative alias** (via `getCwdRelativePathPolicyValues`), not just absolute + canonical.
So the worktree scenario resolves correctly without re-derivation — a relative parent rule (`src/**`) matches the child's relative alias across cwds, while an absolute rule matches only co-located paths (least privilege).
Canonicalization does *not* bridge cwds (git worktrees are real dirs, not symlinks); the relative alias is the bridge.
- **Revises ADR 0005** — the "agent-neutral resolution" section of `0005-serving-authorizer-provenance.md` is the exact decision being changed; the ADR must cross-link and state what 0005 behavior is preserved (recorded-authority-first, escalate-`ask`, provenance-on-the-ask).
- **ADR 0002 boundary** — the wire schema carries strings (`matchValues: string[]`), never `AccessPath` instances, keeping the manager string-based.
- **Release**: mid-batch — defer (batch "cross-session-intent", tail = Step 3 / #597).
Both plan-execution commits are `docs:` (hidden), so this step cuts no release on its own.
- **No follow-up issues filed** — Steps 2 (#596) and 3 (#597) already exist as the implementation of this contract.
- Next stage is `/build-plan` (docs-only, no test cycles).
## Stage: Planning revision (2026-07-18T00:47:18Z)
### Session summary
The operator raised a meta-level doubt — whether the decisions felt hard due to a comprehension gap or a missing reframe — and an advisory dialogue surfaced the unifying principle: **the child owns the facts; the parent owns the judgment**.
The plan was amended (commit `88d2c886`) to restructure the ADR principle-first, add a composition section, and name two explicitly deferred edges.
### Observations
- **The reframe was real, not over-application.**
The principle retro-explains decisions made independently — ADR 0005's serving-is-resolution (judgment repeats at each node) and ADR 0007's chain (plurality of judges within a node) — and derives all three previously confirmed parameters (child-fixed match set = facts at origin; agent-scoped serving = parent's judgment; `ask` floor = no facts → no judgment → escalate).
Three independent confirmations distinguished a genuine reframe from a concept stretched too far.
- **The unified model** (validated against `authorizer.ts` / `authorizer-selection.ts`): authorization is a walk up a session tree; at each node an ordered sequence of judges (recorded authority → Track B chain links → terminal) examines the same fixed facts; the only inter-node operation is the courier move (`ParentAuthorizer`), which carries facts and never judgment.
Key clarification for the operator's mental model: `ask` is *recorded authority's* non-definitive outcome, not an Authorizer's — today each session selects exactly one Authorizer, and every Authorizer answers definitively; the "set of Authorizers that may defer" is the Track B future (ADR 0007), which the operator's intuition anticipated.
- **`ParentAuthorizer` is a courier, not a judge** — it occupies the `Authorizer` slot structurally but exercises no judgment; this explains *why* ADR 0005 made serving re-run resolution.
- **Synergy consequence recorded**: once Tracks A and B both land, a serving node's chain links (e.g. the model judge) review forwarded asks against the child-fixed fact set — honest evidence, not a parent-side re-derivation.
- **Two guards against over-application baked into the plan**: the composition section is descriptive-only (cites `docs/decisions/0007-model-judge-authorizer-chain-adr.md` by path, decides nothing new), and the deferred-edges section names where the model is known-incomplete (single-surface fact set — #565 item 3; multi-hop principal identity).
- **For the `/build-plan` stage**: the ADR's Decision section leads with the principle; the four former "decisions" are now consequences derived from it; the two deferred edges belong in `## Consequences`, not silently omitted.
## Stage: Implementation — Build (2026-07-18T01:00:00Z)
### Session summary
Executed the docs-only plan in two `docs:` commits: wrote `docs/decisions/0008-cross-session-access-intent.md` (the principle-first ADR) and marked Phase 12 Step 1 complete in `docs/architecture/architecture.md` (heading `✅` + Mermaid `S1` node `✅` + a `Landed:` note).
No `src/`/`test/`/schema/config changes, matching the plan's Non-Goals.
Pre-completion reviewer returned PASS.
### Observations
- **ADR structure landed principle-first** as planned: `## Decision` leads with *the child owns the facts; the parent owns the judgment*, then four numbered consequences (path portability, wire schema, agent-scoped serving, version skew), a `### Composition` subsection situating the record against ADR 0007, and a `### Explicitly deferred edges` subsection.
- **Deviation from the plan (cosmetic)**: the plan's Module-Level Changes placed the two deferred edges under `## Consequences`; the shipped ADR keeps them in a `### Explicitly deferred edges` subsection under `## Decision`, grouped with the four consequences and the composition section.
The reviewer flagged this as a WARN but judged it "arguably better organized"; kept as-is to avoid churn on a cohesive structure.
- **Recorded the resolved decisions in the architecture `Landed:` note** — the pre-decision Target bullet framed open questions ("tolerant read"; "whether `requesterAgentName` participates or serving stays agent-neutral"), which the ADR resolved (required field with `ask` floor; agent-scoped serving).
The `Landed:` note explicitly states it supersedes that speculative framing so the roadmap is not left misleading.
- **Pre-completion reviewer: PASS** — lint clean (Biome/ESLint/rumdl over docs); all four Mermaid blocks parse via `mmdc`; ADR cross-links to ADR 0005 ("Base ruleset (agent-neutral resolution)") and ADR 0007 (chain verdict range, terminal split, enforcement checkpoint) verified accurate against source; no scope creep into re-deciding ADR 0007; the one present-tense "is retired ([#597])" phrase is correctly `[#597]`-qualified, not a false implementation claim.
- **Reviewer warnings**: one non-blocking WARN (deferred-edges subsection placement under `## Decision` vs. the plan's `## Consequences`); cosmetic only.
- **Release**: mid-batch — defer (batch "cross-session-intent", tail = Step 3 / #597).
Both commits are `docs:` (hidden), so this step cuts no release on its own; the batch ships when #597 lands.
- Next step is `/ship-issue`.
## Stage: Final Retrospective (2026-07-18T15:03:31Z)
### Session summary
Shipped issue #595 end to end in one continuous session: planned the ADR, ran two `ask_user` rounds to settle three parameters, then — prompted by the operator's meta-doubt — held an advisory dialogue that surfaced a unifying principle (*the child owns the facts; the parent owns the judgment*), restructured the plan and ADR around it, built the two `docs:` commits, and shipped with the release deferred to the batch tail (#597).
The issue closed on `main`; no release cut (docs-only on `exclude-paths`).
A clean session whose standout event was the reframe dialogue that changed the deliverable's structure, not its decisions.
### Observations
#### What went well
- **Advisory reframe that grounded doubt in code, then generalized (novel win).**
The operator said the decisions "feel difficult… I can't tell if it's a comprehension gap or a missing reframe."
Rather than push forward, each open parameter was grounded in the actual source first: the worktree portability worry dissolved on finding that `AccessPath.matchValues()` already carries a **cwd-relative alias** (via `getCwdRelativePathPolicyValues` in `path-normalization.ts`), so relative parent rules stay portable across cwds without re-derivation.
That grounding then generalized into the principle *child owns the facts; parent owns the judgment*, validated against `authorizer.ts`/`authorizer-selection.ts` — it retro-explained ADR 0005 (serving-is-resolution) and ADR 0007 (the chain) and derived all three confirmed parameters.
The reframe reshaped the ADR (principle-first + a composition section) without changing a single decision.
- **Two-round `ask_user` gate for an ADR issue.**
Round 1 surfaced the three deliberative parameters; round 2 tightened them after grounding in the code (e.g. reframing version-skew from "tolerant read" to "required field + `ask` floor").
The pattern fit the ADR-issue rule in `/plan-issue` exactly and produced decisions the operator could stand behind.
- **Guards against over-application, baked into the artifact.**
When a unifying model is attractive there is a real risk of stretching it; the ADR's composition section was kept descriptive-only (cites ADR 0007 by path, decides nothing new) and a deferred-edges section named exactly where the model is incomplete (single-surface fact set #565 item 3; multi-hop principal identity).
The pre-completion reviewer confirmed no scope creep into re-deciding ADR 0007.
- **Clean incremental verification.**
`rumdl check` ran after each doc write (not just at the end); the green baseline (`check` + `lint`) was confirmed before any edit; pre-completion returned PASS.
#### What caused friction (agent side)
- `other` (self-caught, no rework) — the plan-restructure `Edit` call carried a stray `"newText2": null` key on its first entry (the `oldText2`/`newText2` anti-pattern AGENTS.md documents).
The tool reported "12 block(s)" for 11 intended edits; caught immediately by counting reported blocks against intended edits and re-reading the regions — the exact mitigation AGENTS.md prescribes (Refs #605).
Impact: none — the value was `null` (harmless) and the existing guard worked; evidence the rule is doing its job, not a gap.
- `other` (minor accuracy) — the Build stage note used a rounded placeholder timestamp (`2026-07-18T01:00:00Z`) instead of a real `date -u` value.
Impact: cosmetic; the breadcrumb ordering is still correct, but a real timestamp is trivially better for the cross-session trail.
#### What caused friction (user side)
- **None — the meta-doubt intervention was the session's highest-leverage moment.**
The operator committed the plan, then reopened it with "these decisions feel difficult."
That redirect (a question, not a correction) is exactly the strategic-judgment intervention the workflow wants: it arrived before the ADR was written, when restructuring was cheap, and it produced a materially better artifact.
Nothing to change; recorded as a model of good bidirectional feedback.
### Diagnostic details
- **Model-performance correlation** — the session switched models frequently (operator-driven), with `claude-opus-4-8` and `claude-sonnet-5` present for the heavy reasoning stretches and lighter models (`claude-haiku-4-5`, `claude-fable-5`, `deepseek-v4-flash`) elsewhere.
The one subagent dispatch (`pre-completion-reviewer`) ran on its configured model — judgment-appropriate.
No mismatch to flag: the judgment-heavy reframe work had strong models available.
- **Escalation-delay tracking** — no `rabbit-hole` friction; longest same-target streak was benign (verifying the multi-edit block count).
- **Unused-tool detection** — no `missing-context` gaps; `grep`/`sed`/`Read` grounded every claim in source (the cwd-relative alias finding came from reading `path-normalization.ts` directly).
- **Feedback-loop gap analysis** — verification was incremental: baseline `check`+`lint` before edits, `rumdl check` after each doc write, `lint` + `fallow dead-code` at pre-push, CI watched to green.
### Changes made
1. `packages/pi-permission-system/docs/retro/0595-adr-0008-cross-session-access-intent.md` — appended this Final Retrospective stage entry.
No `AGENTS.md` or prompt changes: the operator confirmed landing the retro file only, since the session's friction was already covered by existing rules (the `newText2` block-count guard, Refs #605) or too trivial to encode (a placeholder timestamp).