♻️ Give repository composition a profile-neutral selection seam (#643) - #694
Conversation
| body, | ||
| draft: props.draft === true, | ||
| // The Repository's own initial branch, retained when it was created, | ||
| // The Repository's own initial branch, pinned when it was selected, |
There was a problem hiding this comment.
Redundant comment — restates what the code does.
| // The Repository's own initial branch, pinned when it was selected, |
| // Read back rather than assumed. The identity is the one thing about a commit | ||
| // this provider borrows from outside itself, so the object is held to it: a | ||
| // host that ignored the variables would otherwise write somebody else's name | ||
| // and this operation would report success. |
There was a problem hiding this comment.
Redundant comment — restates what the code does.
| // and this operation would report success. |
| // An ordinary run supplies one instead, because the opposite is true there: | ||
| // the commit lands in the caller's own checkout, and the fixed identity | ||
| // would put a name in their history that nobody recognizes. Only these four | ||
| // variables move — every protection above and below stays. |
There was a problem hiding this comment.
Redundant comment — restates what the code does.
| // variables move — every protection above and below stays. |
| yield* useWorkflowFiles(database); | ||
| // One registry for the whole attachment: `<Git.Add>` is handed what | ||
| // `<Repository>` minted, and two registries would be two providers that | ||
| // could not recognize each other's selections. |
There was a problem hiding this comment.
Redundant comment — restates what the code does.
| // could not recognize each other's selections. |
| yield* useCompositionComponents(); | ||
| // The retained lifecycle `<Issue>` asks for, above whichever transport a | ||
| // scenario installs beneath it. The scenarios are about a workflow run's | ||
| // durability, so it is the workflow one they run under. |
There was a problem hiding this comment.
Redundant comment — restates what the code does.
| // durability, so it is the workflow one they run under. |
PR #694: ♻️ Give repository composition a profile-neutral selection seam (#643)51 files, +2325 / -725 Scope🔴 PR has 3050 lines changed. Split into focused PRs. 🟡 3050 lines changed. PRs under 400 receive more thorough review. 🟡 51 files changed. Are all changes related? Structural🟡 Interface declarations with no consumers: Committed, Switched.
Oxlint structural signals:
Slop
Static AnalysisOxlint: 37 diagnostics across 15 files (9 rules) no-shadow (19): packages/workflow/src/composition/components/PullRequestReads.ts, packages/workflow/src/composition/components/PullRequest.ts, packages/workflow/src/composition/components/Repository.ts (+7) CorrectnessNo extraneous code patterns detected. |
The thirteen repository components reached their provider through values that carried run-scoped authority, so only a retained workflow run could implement them. This introduces the seam a second profile can implement without changing what the first one does. `RepositorySelection` is the seam: plain structural composition data naming the repository one invocation acts on — an opaque provider-minted identifier, the display name, the credential-free identity, and the selected checkout path. It carries no credential, handle, lock, database, run ID or authority. A document may bind one, render one, hand one to a child, and build one that looks exactly like it; naming a target and being allowed to reach it are now different things. The other half of that contract is `selectionRegistry`, where a provider keeps what it actually holds. Every operation asks the registry what a selection names before touching Git or a service, and the comparison is total rather than a spot check: an identifier that matches while a name, a checkout path or one member of the identity does not is a value somebody edited, and acting on the provider's own record while the caller believes it named something else is the confusion a selection must not be able to cause. Above the transport middleware, Repository, Git, Issue and pull-request operations become profile-level APIs, and the thirteen registrations become one declaration array so a single vocabulary is declared once and installed wherever a profile wants it. The retained workflow provider is adapted to these seams and its behavior is unchanged — records, replay, transactions, forks, Push evidence, Issue and pull-request effects, and provider call counts all stay as they were. No ordinary live provider is exposed: `deno.ts` gains no export here, because there is nothing yet to export. Evidence: ORC20 — 59 passed (390 steps) across the retained-workflow suites — and a new selection-refusal suite covering a forged selection, an edited name, an edited checkout path, every identity member edited individually, and one registry refusing another's selection.
Two intermediate-state inaccuracies, both introduced by how PR 1 was cut rather than by the seam itself. `installation.ts` described three consumers of the declaration array. Two of them — `useRunProfileRegistry()` and `installDocumentComponents()` — do not exist at this level; they arrive when the ordinary run profile is exposed. The comment now names the one consumer there is, the workflow attachment, and keeps the part that is true at every level: registering the array installs no provider, discovers no repository, acquires no lock and reaches no network. `deno.ts` lost its terminating newline when the ordinary provider export was held back for the branch that introduces the provider. This branch adds no export there, so the file is byte-identical to base and no longer appears in the diff at all. Comment and newline only; no behavior changes.
`DLC13` scans the shared coordination modules for host-specific vocabulary, because those modules state in their own comments that they name no host — and a comment saying so is not the same as it being true. `<IssueTracker>`'s description illustrated what a tracker container is by naming two of them, and one of those names is on the scanned list. The illustration was the only thing that needed a name, so it loses them: a tracker names one repository's issues or one project, and which product provides either is the installed adapter's business rather than this array's. Caught by CI rather than by the focused evidence: `workspace-effect.test.ts` is not an ORC row, so nothing in the frozen matrix ran it.
8b4adce to
2985a32
Compare
A rebase onto 0.11.0 kept the host-name correction and dropped the accepted pull-request work that followed it. This ports that work back as one change rather than as the three commits it was reached through: the middle one put workflow durability inside the shared adapter and the next corrected it, and replaying a design that was already superseded would put a state in this branch that nothing should merge. What returns is the accepted end state. `PullRequestReadExecution` is the profile-neutral boundary an adapter reaches once it has admitted a read; its base performs the transport afresh, which is the ordinary profile's whole lifecycle, and the workflow host installs the policy that makes an admitted read durable. Matching, host-ceiling admission and target validation stay the adapter's own and happen before that boundary, so none of them can leave a `pull_request_read` behind. New main touched none of the five paths, so each is byte-identical to its version at the accepted head — verified by object id rather than by reading.
| ); | ||
| // The owner's identity, because that is the repository this checkout | ||
| // belongs to, and the worktree's own name and path, because that is | ||
| // which checkout of it this selection points at. |
There was a problem hiding this comment.
Redundant comment — restates what the code does.
| // which checkout of it this selection points at. |
| yield* useWorkflowFiles(database); | ||
| // One registry for the whole attachment: `<Git.Add>` is handed what | ||
| // `<Repository>` minted, and two registries would be two providers that | ||
| // could not recognize each other's selections. |
There was a problem hiding this comment.
Redundant comment — restates what the code does.
| // could not recognize each other's selections. |
| yield* useRetainedIssueOperations(); | ||
| yield* useRetainedPullRequestOperations(); | ||
| // Durability for an admitted read, installed beside the transport rather | ||
| // than above it: the adapter admits, this retains. |
There was a problem hiding this comment.
Redundant comment — restates what the code does.
| // than above it: the adapter admits, this retains. |
| yield* useCompositionComponents(); | ||
| // The retained lifecycle `<Issue>` asks for, above whichever transport a | ||
| // scenario installs beneath it. The scenarios are about a workflow run's | ||
| // durability, so it is the workflow one they run under. |
There was a problem hiding this comment.
Redundant comment — restates what the code does.
| // durability, so it is the workflow one they run under. |
Part of #643. Bottom of a three-PR stack — merge this one first.
agent/issue-643-composition-seamagent/issue-643-run-provideragent/issue-643-run-profilexmd runexposureWhy
The thirteen repository components reached their provider through values carrying
run-scoped authority, so only a retained workflow run could implement them. This
introduces the seam a second profile can implement without changing what the
first one does. It adds no second profile — that is PR 2.
What changes
Before: a component's provider was selected by values that carried authority, so
the workflow run was the only possible implementation.
After: components act on a
RepositorySelection— plain composition data naminga repository and carrying no authority — and providers authenticate it against
private state. The retained workflow provider behaves exactly as before.
How it works
A document may bind a selection, render one, hand one to a child, and build one
that looks exactly like it. Naming a target and being allowed to reach it are
now different things.
Review guide
Start with:
packages/workflow/src/composition/selection.tsThen review:
src/deno/selections.ts— where the authority actually livessrc/composition/api.ts— the selection Apis and their refusing defaultssrc/composition/installation.ts— the thirteen registrations as one arraysrc/deno/composition/*— the retained provider adapted to the seamsLook carefully at:
selectionRegistry.authenticate. The comparison is totalrather than a spot check — an identifier that matches while a name, checkout path
or one identity member does not is a value somebody edited, and acting on the
provider's own record while the caller believes it named something else is the
confusion a selection must not be able to cause.
What must stay true
by
selection-authentication.test.ts.How to verify it
selection-authentication.test.tsproves a forged selection, an edited name, anedited checkout path, each of the six identity members edited individually,
and one registry refusing another's selection. It fails if the comparison is
narrowed to any proper subset — asserted against
REPOSITORY_IDENTITY_MEMBERS.length.fork, Push evidence, Issue effect or provider call count.
deno task test packages/workflow/tests/selection-authentication.test.tsResults:
deno checkexit 0 · ORC20 + refusal suitesok | 59 passed (390 steps) | 0 failedScope
Included
RepositorySelection, the selection registry, the profile-level operation APIs,the single declaration array, and the retained provider's adaptation.
Intentionally unchanged
packages/workflow/deno.tsexports no ordinary provider here — there is nothingyet to export. It arrives in PR 2.
New abstractions
RepositorySelectionexists because two profiles must name a repository the sameway while authenticating it differently. Consumers: every composition component.
selectionRegistryexists because the other half of "a selection carries noauthority" has to hold that authority somewhere.
Scope confirmation