Skip to content

♻️ Give repository composition a profile-neutral selection seam (#643) - #694

Merged
taras merged 4 commits into
mainfrom
agent/issue-643-composition-seam
Sep 1, 2026
Merged

♻️ Give repository composition a profile-neutral selection seam (#643)#694
taras merged 4 commits into
mainfrom
agent/issue-643-composition-seam

Conversation

@taras

@taras taras commented Sep 1, 2026

Copy link
Copy Markdown
Owner

Part of #643. Bottom of a three-PR stack — merge this one first.

Order PR Branch What it adds
1 (this) agent/issue-643-composition-seam the profile-neutral seam; retained workflow unchanged
2 depends on this agent/issue-643-run-provider the ordinary provider, reachable only from tests
3 depends on 2 agent/issue-643-run-profile root and nested xmd run exposure

Why

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 naming
a repository and carrying no authority — and providers authenticate it against
private state. The retained workflow provider behaves exactly as before.

How it works

component → RepositorySelection (names a target) → provider → selectionRegistry (holds authority) → Git/service

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.ts

Then review:

  1. src/deno/selections.ts — where the authority actually lives
  2. src/composition/api.ts — the selection Apis and their refusing defaults
  3. src/composition/installation.ts — the thirteen registrations as one array
  4. src/deno/composition/* — the retained provider adapted to the seams

Look carefully at: selectionRegistry.authenticate. The comparison is total
rather 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

  • A selection carries no authority — enforced by the registry holding it, checked
    by selection-authentication.test.ts.
  • The retained workflow behaves identically — checked by the ORC20 suites.

How to verify it

  • selection-authentication.test.ts proves a forged selection, an edited name, an
    edited 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.
  • The ORC20 suites fail if the adaptation changed any record, replay, transaction,
    fork, Push evidence, Issue effect or provider call count.
deno task test packages/workflow/tests/selection-authentication.test.ts

Results: deno check exit 0 · ORC20 + refusal suites ok | 59 passed (390 steps) | 0 failed

Scope

Included

  • RepositorySelection, the selection registry, the profile-level operation APIs,
    the single declaration array, and the retained provider's adaptation.

Intentionally unchanged

  • Every retained workflow behavior.
  • packages/workflow/deno.ts exports no ordinary provider here — there is nothing
    yet to export. It arrives in PR 2.

New abstractions

  • RepositorySelection exists because two profiles must name a repository the same
    way while authenticating it differently. Consumers: every composition component.
  • selectionRegistry exists because the other half of "a selection carries no
    authority" has to hold that authority somewhere.
  • Each new abstraction has multiple concrete uses.
  • No speculative functionality is included.

Scope confirmation

  • Every changed file supports the purpose described above.
  • Unrelated cleanup and formatting changes are excluded.
  • Generated or mechanical changes are clearly identified.
  • The description matches the final diff and test results.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Found 5 redundant comments. Inline suggestions to remove them below.

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,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// 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.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// 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.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// 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.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// 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.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// durability, so it is the workflow one they run under.

@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown

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.
Symbol Declared at Refs in diff Why flagged
Committed packages/workflow/src/deno/composition/commit.ts:119 1 referenced ≤1× within the added diff (pre-existing usages not counted)
Switched packages/workflow/src/deno/composition/switch.ts:79 1 referenced ≤1× within the added diff (pre-existing usages not counted)

Oxlint structural signals:

  • no-unused-vars ×6: packages/workflow/src/deno/composition/provider.ts, packages/workflow/tests/support/issue-scenario.ts, packages/workflow/tests/support/pull-requests.ts
  • no-unnecessary-boolean-literal-compare ×1: packages/workflow/tests/support/issue-scenario.ts

Slop

  • packages/workflow/src/deno/workspace/host.ts:162 (removed)
  • packages/workflow/src/deno/composition/provider.ts:294// which checkout of it this selection points at.
  • packages/workflow/src/deno/workspace/host.ts:165// could not recognize each other's selections.
  • packages/workflow/src/deno/workspace/host.ts:182// than above it: the adapter admits, this retains.
  • packages/workflow/tests/support/issue-scenario.ts:166// durability, so it is the workflow one they run under.

Static Analysis

Oxlint: 37 diagnostics across 15 files (9 rules)
Density: 0.016 violations/added-line

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)
no-unused-vars (6): packages/workflow/src/deno/composition/provider.ts, packages/workflow/tests/support/issue-scenario.ts, packages/workflow/tests/support/pull-requests.ts
no-base-to-string (5): packages/workflow/tests/support/issue-scenario.ts
consistent-return (2): packages/workflow/src/deno/composition/push.ts
no-useless-fallback-in-spread (1): packages/workflow/src/deno/composition/host.ts
no-useless-spread (1): packages/workflow/src/issue/effect.ts
no-array-sort (1): packages/workflow/tests/support/issue-scenario.ts
unbound-method (1): packages/workflow/tests/support/pull-requests.ts
no-unnecessary-boolean-literal-compare (1): packages/workflow/tests/support/issue-scenario.ts

Correctness

No 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.
@taras
taras force-pushed the agent/issue-643-composition-seam branch from 8b4adce to 2985a32 Compare September 1, 2026 19:41
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.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Found 4 redundant comments. Inline suggestions to remove them below.

);
// 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.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// 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.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// 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.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// 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.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// durability, so it is the workflow one they run under.

@taras
taras merged commit 1721c06 into main Sep 1, 2026
30 checks passed
@taras
taras deleted the agent/issue-643-composition-seam branch September 1, 2026 21:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant