Skip to content

Classify artifact references in one place - #2479

Merged
chubes4 merged 1 commit into
mainfrom
refactor/2432-artifact-kernel
Sep 3, 2026
Merged

Classify artifact references in one place#2479
chubes4 merged 1 commit into
mainfrom
refactor/2432-artifact-kernel

Conversation

@chubes4

@chubes4 chubes4 commented Sep 3, 2026

Copy link
Copy Markdown
Collaborator

Part of #2432. A bounded slice: one canonical artifact reference classification, not the full artifact kernel.

Problem

Artifact reference groups were derived twice in TypeScript, with rules that had already drifted:

group agent-task-run-result.ts:116 artifact-references.ts:197
artifact_bundles artifact-bundle / codebox-artifact-bundle identical logic, restated
changed_files declared kind only kind + changed-files + path match
patches declared kind only kind + patch + path match
transcripts === codebox-transcript kind.includes("transcript")
logs two declared kinds includes("log") + .log / .jsonl

The same bundle could group differently depending on which projection a consumer read.

Change

Add artifact-ref-classification.ts. Both projections now compose their group sets from shared predicates, so what makes a reference a patch, a transcript, or a log is decided once. Each surface still owns the group shape it publishes.

The difference between them is a trust boundary, not drift

This is the important part, and it is why the predicates take an explicit mode.

normalizeAgentTaskRunResult feeds the workspace delta, and a delta patch is something a caller may apply. It must therefore accept only explicitly declared kinds: a file that merely sits at files/patch.diff must never earn that trust. publicArtifactRefGroups is a read-only discovery projection, where inferring from paths keeps partially typed bundles listable.

My first attempt collapsed both into the lenient rule and weakened that boundary. tests/agent-task-contracts.test.ts:277 caught it:

assert.equal(inferredFilenameDelta.status, "unavailable", "workspace delta only accepts explicitly typed artifacts")

So the modes are now named and documented in the module rather than left as an accidental difference between two copies. tests/artifact-ref-classification.test.ts locks the distinction from both directions so a future consolidation cannot quietly erase it.

Latent bug fixed

The shared path helper compares trailing path segments (path === suffix || endsWith("/" + suffix)). The log checks passed extensions to it, so pathEndsWith(path, ".jsonl") only ever matched a file literally named .jsonl. Real filenames like files/run.jsonl never classified as logs. Added a separate extension helper. This slightly widens the discovery logs group, which is the documented intent of that predicate.

Verification

  • npm run build passes.
  • npm run check passes: 311 commands, including the PHP smoke lane.
  • New tests/artifact-ref-classification.test.ts asserts typed mode rejects path-inferred patches and changed files, discovery mode accepts them, declared kinds work in both, and the boundary holds end to end through workspaceDeltaFromAgentTaskRunResult and publicArtifactRefGroups.

Follow-up found while investigating

A third implementation of this projection lives in PHP. class-wp-codebox-host-run-result-normalizer.php rebuilds the whole wp-codebox/agent-task-run-result/v1 result by hand, because the plugin shells out to recipe-run and re-derives the agent-task result host-side. Details in a comment on #2432. That is a plugin architecture question tied to #1827 and should be scheduled deliberately rather than folded into this slice.

AI assistance

  • Model: Claude Sonnet 4.5
  • Tool: OpenCode
  • Used for: Located the duplicate classification, attempted a naive consolidation, investigated the resulting test failure to identify the trust boundary, redesigned around explicit modes, found and fixed the extension-matching bug, and ran build/check under Chris Huber's direction.

Artifact reference groups were derived twice: agent task run results matched
exact kinds, and the public reference projection matched broader predicates.
The rules had drifted, so the same bundle could group differently depending on
which projection a consumer read.

Add one classification module both projections compose their groups from. The
difference between them is preserved and named: typed classification accepts
only declared kinds because it feeds the workspace delta, whose patches a
caller may apply, while discovery classification also infers from paths so
partially typed bundles stay listable.

Fix log extension matching while consolidating. The shared path helper compares
trailing path segments, so the .log and .jsonl checks never matched a real
filename.
@chubes4
chubes4 marked this pull request as ready for review September 3, 2026 20:21
@chubes4
chubes4 merged commit b26b5d3 into main Sep 3, 2026
5 checks passed
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