Skip to content

fix(converter): hold parity with harbor 0.24.0's converters - #41

Merged
laithalsaadoon merged 2 commits into
mainfrom
factory/2-harbor-0-24
Oct 8, 2026
Merged

laithalsaadoon merged 2 commits into
mainfrom
factory/2-harbor-0-24

Conversation

@laithalsaadoon

Copy link
Copy Markdown
Owner

Move the harbor oracle to 0.24.0 and hold parity with its converter changes

Branch factory/2-harbor-0-24, two commits on origin/main 5d4efa3:

  • 36712c2 chore(deps): move the harbor oracle to 0.24.0 with a cooldown exemption
  • 793087b fix(converter): hold parity with harbor 0.24.0's converters

harbor 0.24.0 (PyPI upload 2026-10-05T05:04:46Z) leaves the ATIF models, the validator and RFC 0001 byte-identical to 0.23.0 (ATIF-v1.8 both sides) and changes the converters atif-sql ports. The lock bump surfaced three converter changes, one more than the plan named: harbor #2972 landed on 2026-09-12 after 0.23.0 was cut.

Parity decisions, one per upstream change

upstream decision the test that shows it
harbor-framework/harbor#3434 (6a410975), Claude subagent ids on ATIF steps adopt. _event_identity_extra puts is_sidechain and the event's agentId on every step it becomes (prompt, tool result, turn), right after id, harbor's key order. harbor reads agentId now, so the oracle's one deliberate divergence (steps[i].extra.agent_id: only in ours) is removed from DELIBERATE_DIVERGENCES, and a snake_case agent_id key names no subagent, as in harbor. test_claude_code_conversion.py::TestSidechains::test_every_step_of_a_subagent_names_it (harbor's own #3434 test, two concurrent subagents), ::test_snake_case_agent_id_is_not_an_identity, test_result_signals.py::TestSubagentAgentId::test_harbor_names_every_sidechain_step_the_same (replaces the test that required the divergence)
harbor-framework/harbor#3467 (d286d15d), Codex structured tool outputs as text and images adopt, with one documented divergence. Before: a list output was Python's str() of the list ("[{'type': 'input_text', ...}]"), 5,124 such outputs in the 144 local rollouts. After: text parts joined by \n, other blocks as JSON text, a non-string output as JSON text, a JSON scalar as its raw text, remote and unreadable images as harbor's own [image: url] / [image omitted]. Divergence: an inline image harbor saves to <logs_dir>/images/codex_<sha16>.<ext> and returns as content parts becomes the blob placeholder the pre-pass writes for the same item, because the converter is pure and the corpus keeps attachments in its blob store. The oracle forgives only that content path, only when each placeholder's SHA-256 starts with harbor's file name digest, the media type matches, and every text part matches (harbor_oracle._image_content_forgiven). test_codex_conversion_port.py::TestStructuredToolOutput (13 cases, live cross-checked except the two inline-image ones), ::test_harbor_differs_only_by_the_saved_image (exactly one diff path, forgiven, and a tampered digest is not), ::test_an_inline_image_is_the_pre_pass_placeholder (raw and production paths give identical trajectories), 4 new test_function_call_output_blob_shapes rows
harbor-framework/harbor#2972 (7e042f6e), Codex web search ids adopt. A web_search_call keeps its item id, or gets <api call>_web_search_<n>, where its id was "". test_codex_conversion_port.py::TestToolCallShapes::test_web_search_call_gets_a_request_scoped_id_and_null_observation, ::test_web_search_call_keeps_codexs_own_item_id
trial/sync_trajectory.py (#3467) and 536f533a (CODEX_HOME out of tmp, main after 0.24.0) nothing: streamed-trial media copying and runtime home setup, no conversion effect. none needed

Cooldown

[tool.uv] gets exclude-newer-package = { harbor = "2026-10-05T05:05:00Z" }, the key shape in uv's settings reference (docs.astral.sh/uv/reference/settings, read 2026-10-08) for the mise-pinned uv 0.12.23; the global exclude-newer = "7 days" is unchanged. The comment says to delete the exemption in the next uv lock --upgrade after 2026-10-12T05:05Z. Semgrep uv-missing-dependency-cooldown (rule fetched from semgrep/semgrep-rules develop) reports nothing on the branch's pyproject.toml and flags line 109 on a planted exclude-newer = "3 days".

Lock

uv lock --upgrade-package harbor: harbor 0.23.0 -> 0.24.0 and one added package, semver 3.1.0 (a new harbor 0.24.0 dependency, uploaded 2026-09-12, dev-only through harbor). No other package changes version. uv 0.12.23 writes revision = 5 (was 3) on any re-resolution, a backward-compatible lockfile revision.

Converter version

CONVERTER_SCHEMA_VERSION 6 -> 7 with a history line: UPSTREAM_VERSION moves to 0.24.0 (stamped as meta.json harbor_version), and all three adoptions change artifact bytes. Digest re-pinned in test_converter_schema_version.py. The next materialize re-converts the whole corpus. The vendored files change only their header line (Vendored from harbor 0.24.0). Goldens are not re-frozen: the synthetic fixtures exercise none of the three shapes, and test_goldens shows the 0.24.0 live oracle still matches them.

Gates

gate before (origin/main + 0.23.0) after (793087b)
test_harbor_oracle.py 6 passed, 1 skipped 6 passed, 1 skipped
test_parity_codex.py 2 passed 2 passed
test_parity_claude_code.py 3 passed 3 passed
test_parity_live.py (25 + 25 newest sessions) 2 passed 2 passed
test_vendored_atif.py 21 passed 21 passed
live parity, all 144 Codex rollouts (ATIF_PARITY_LIMIT=0) not run passed
live parity, 150 newest ~/.claude/projects sessions not run passed
mise run check green, 2396 passed, 1 skipped in 277.63 s
mise run lock:check green
mise run docs:install && mise run docs:gate (docs/CONTRACT.md changed) green, astro check 0 errors, 64 vitest tests

With harbor 0.24.0 and no converter change (commit 36712c2 alone), 5 tests fail: the web search port test, both live parity tests (22 of 25 Claude sessions on extra.agent_id, 18 of 25 Codex rollouts on list content), the result-signals divergence test and the upstream version check. So 36712c2 is red on its own and 793087b is green; rebase-merging lands one red commit on main, squash-merging does not.

Risks

  • Every Codex session with a list tool output changes content text on re-conversion (more readable, and searches over tool_results.content see plain text instead of a Python repr). A query that matched the repr's 'type': 'input_text' breaks.
  • An MCP image block with inline data that reached production (none in the local corpus) becomes a placeholder whose bytes the blob store does not hold, because extract_codex_blobs lifts input_image data URLs only.
  • Docs under docs/insights, docs/architecture and docs/diagrams still name harbor 0.23.0 in passing; AGENTS.md, CONTRIBUTING.md, the converter README, docs/CONTRACT.md and the package comments name 0.24.0.

What to look at

  • packages/atif-converter/tests/harbor_oracle.py: _image_content_matches is the only new forgiveness; check it cannot pass a changed text part.
  • packages/atif-converter/src/atif_converter/domain/codex_conversion.py _parse_output_blob through _image_media_type against harbor 0.24.0 codex.py lines 611-766.
  • Delete the exclude-newer-package line in the first uv lock --upgrade after 2026-10-12.

Lead review (2026-10-08)

  • Diff read against the plan: 28 files, the vendored ATIF models, validator and RFC are header-only changes (harbor's blobs are identical at v0.23.0 and v0.24.0), and uv.lock moves harbor and adds semver 3.1.0 alone.
  • Re-ran mise run install:ci and mise run check in a fresh worktree at 793087b: green, 2396 passed, 1 skipped in 256 s, pyright 0 errors.
  • Probed _image_content_matches directly: a matching placeholder is forgiven; a changed text part, a wrong media type and a wrong SHA-256 are not.
  • No Codex rollout under ~/.codex/sessions carries an MCP image block with inline data, so the dangling-placeholder risk above has no case today; a follow-up can lift those blocks in extract_codex_blobs.
  • Merge by squash: 36712c2 alone is red, so a rebase merge would land a red commit on main.

🤖 Generated with Claude Code

laithalsaadoon and others added 2 commits October 8, 2026 20:04
harbor 0.24.0 (PyPI upload 2026-10-05T05:04:46Z) changes both converters
atif-sql holds parity with: #3434 keeps Claude subagent ids on every ATIF
step, #3467 records Codex structured tool outputs as text and images, and
#2972 gives Codex web search calls an id. The ATIF models, the validator and
RFC 0001 are byte-identical to 0.23.0.

The workspace's seven-day cooldown admits 0.24.0 only from
2026-10-12T05:05Z, so harbor alone gets an exclude-newer-package exemption
up to 2026-10-05T05:05:00Z, the instant its upload needs; the global window
stays seven days. Delete the exemption in the next `uv lock --upgrade` after
2026-10-12, when the window reaches the release.

`uv lock --upgrade-package harbor` moves harbor alone and adds semver 3.1.0,
a new harbor dependency uploaded 2026-09-12; every other package stays where
main locks it. uv 0.12.23 writes lockfile revision 5 on any re-resolution.

The parity decisions for the three converter changes follow in the next
commit; on this commit alone the oracle tests report them as failures.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
harbor 0.24.0 changes three things the converters port. Each is decided
here against the upstream diff.

- harbor #3434 (6a410975), adopted: every step a Claude Code event becomes,
  its prompt, tool result and turn alike, carries `is_sidechain` and the
  event's `agentId` as `extra.agent_id`, right after `id`, through
  `_event_identity_extra`. harbor now reads `agentId`, so the oracle's one
  deliberate divergence (`steps[i].extra.agent_id` only in ours) is gone,
  and a snake_case `agent_id` key names no subagent, as in harbor.
- harbor #3467 (d286d15d), adopted with one divergence: a Codex tool output
  that is a list of MCP content blocks or content items becomes its text
  parts joined by newlines rather than Python's str() of the list (5,124 such
  outputs in the 144 local rollouts); a non-string `output` is its JSON text;
  a JSON scalar keeps its raw text. An inline image, which harbor saves under
  `<logs_dir>/images/` and returns as content parts, is the blob placeholder
  the pre-pass writes for the same item, because the converter is pure and
  the corpus keeps attachments in its blob store. The oracle forgives exactly
  that content path, only when each placeholder names the bytes harbor saved
  (`_image_content_forgiven`).
- harbor #2972 (7e042f6e), adopted: a Codex `web_search_call` keeps its item
  id, or gets `<api call>_web_search_<n>`, where its id was empty.

`trial/sync_trajectory.py` (#3467) copies media for streamed trials and
536f533a (`CODEX_HOME` out of tmp, after 0.24.0) sets up the runtime home;
neither touches a conversion.

The vendored ATIF models, validator and RFC are byte-identical to 0.24.0's,
so only their headers and `UPSTREAM_VERSION` move, which `meta.json` stamps
as `harbor_version`. CONVERTER_SCHEMA_VERSION 7 re-converts the corpus for
all of it; the digest is re-pinned. The goldens do not change: the synthetic
fixtures exercise none of the three shapes, and the live oracle still matches
them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@laithalsaadoon
laithalsaadoon merged commit 974f5f7 into main Oct 8, 2026
17 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