Skip to content

feat(conditions): generic evaluator + migrate Codex onto conditions-core (PR-2) - #326

Merged
Juliusolsson05 merged 2 commits into
integration/conditions-frameworkfrom
feat/conditions-codex-migration
Jun 22, 2026
Merged

Juliusolsson05 merged 2 commits into
integration/conditions-frameworkfrom
feat/conditions-codex-migration

Conversation

@Juliusolsson05

@Juliusolsson05 Juliusolsson05 commented Jun 21, 2026 •

Copy link
Copy Markdown
Owner

PR-2 of the conditions framework

Migrate Codex's headless conditions onto the new generic conditions-core, proven byte-for-byte against the current working implementation. Cross-repo: the packages/codex-headless submodule PR + this gitlink bump.

⚠️ Merge order (gitlink hazard): the codex-headless submodule PR Juliusolsson05/codex-headless#17 MUST merge before this gitlink (c2f5dc4) is valid on integration/conditions-framework. Until then the submodule commit only exists on the submodule PR branch.

1. Generic headless evaluator — src/shared/conditions-core/evaluator.ts

makeEvaluator<P,I>(provider, modules, now) returns { evaluate, keyOf, changed, resolveAction }:

  • evaluate(inputs) loops the modules; for each, state = detect(inputs); skip on null; else insert conditions[kind] = { kind, state, actions: actions(state) } in module-array order.
  • keyOf(snap) = JSON.stringify(snap.conditions) — IDENTICAL to the legacy codexConditionSnapshotKey (ts excluded, keys NOT sorted; insertion order is observable and load-bearing).
  • changed(key) is the dedupe latch (seeded '{}', matching the old lastConditionKey).
  • resolveAction routes a custom action to the first module with a resolve — dormant today, wired for the later answering driver.

Pure; clock injected via now() so the golden can pin ts.

2. Vendor-sync — scripts/sync-conditions-core.mjs

Submodules can't import the app's src/shared, so the headless slice only (contract.ts + evaluator.ts, NOT the React view.ts/ConditionOutlet.tsx/dispatch.ts) is copied into each headless package's src/conditions/core/, each header-stamped // GENERATED by scripts/sync-conditions-core.mjs — edit src/shared/conditions-core, do not edit here. codex-headless is synced in this PR; claude-code-headless's destination is wired (sync: false) so PR-3 reuses the script unchanged.

3. Codex migration (in the submodule PR)

  • trustDialogModule + approvalModule via defineModule, preserving the EXACT two-source approval merge (screen approval + rollout approvalMetadata) and the exact pty actions (\r, p, \x1b; trust accept/2\r).
  • CODEX_MODULES = [trustDialogModule, approvalModule] — order preserved (trust first).
  • codex.switch-model-prompt left as-is (typed, no detector) — unchanged.
  • evaluateCodexConditions + codexConditionSnapshotKey kept exported (re-implemented via the evaluator).
  • CodexHeadless.publishConditionSnapshot repointed to a long-lived makeEvaluator('codex', CODEX_MODULES, () => Date.now()). Emitted snapshot shape + both events byte-identical; legacy per-event trust-dialog emission untouched.

4. Byte-for-byte golden matrix

Throwaway script (not committed) ran OLD (inline copy of prior builders) vs NEW over the matrix; assert JSON.stringify(snapshot.conditions) identical (normalize only ts; keys NOT sorted):

Case Result
trust hidden, no approval PASS
trust visible, no approval PASS
approval screen-only PASS
approval metadata-only PASS
approval screen+metadata PASS
approval metadata-cleared (screen present, meta null) PASS
trust visible + approval screen+metadata (both live, order) PASS
approvalMetadata undefined (omitted) PASS
screen with null command + metadata PASS

ALL PASS.

Typecheck (vs integration baseline)

  • agent-code web (tsc -b tsconfig.web.json, cleared .tsc-out): 7 errors — unchanged from baseline (all pre-existing, unrelated).
  • agent-code node (tsc -b tsconfig.node.json, cleared .tsc-out): 4 errors — unchanged from baseline.
  • codex-headless (tsc --noEmit + full build): 1 error — unchanged from baseline (SemanticChannel.ts:283, pre-existing).
  • Zero NEW errors anywhere.

Submodule gitlink: packages/codex-headless → c2f5dc4f61799380c9256b8fd7d2663f86b6aea3.

🤖 Generated with Claude Code


⚠️ Merge order & method (required — gitlink safety):

  1. Merge codex-headless#17 FIRST, with a merge commit (--merge), never squash/rebase — squash/rebase mints a new SHA and orphans gitlink commit 9480f580, breaking git submodule update on fresh clones.
  2. Do not "Update branch"/rebase Add dispatch mode layout #17 before merging.
  3. Then merge this PR into integration/conditions-framework with --merge.

Juliusolsson05 and others added 2 commits June 21, 2026 23:23
…ore (PR-2)

Add the GENERIC HEADLESS EVALUATOR (src/shared/conditions-core/evaluator.ts):
makeEvaluator(provider, modules, now) loops the module registry, assembles the
unified ConditionSnapshot, and owns the dedupe latch. keyOf is IDENTICAL to the
legacy codexConditionSnapshotKey (JSON.stringify of the conditions map, ts
excluded, insertion order preserved — order is a wire contract). Pure; clock
injected via now() for deterministic golden testing.

Add scripts/sync-conditions-core.mjs: vendors ONLY the headless slice
(contract.ts + evaluator.ts, NOT the React view/outlet/dispatch) into each
headless submodule's src/conditions/core/, header-stamped GENERATED. Submodules
can't import the app's src/shared, so the contract+evaluator are copied in and
this script is the single source of that copy. codex-headless is synced now;
claude-code-headless's destination is wired (sync:false) for the next PR.

Bump the packages/codex-headless gitlink to the migration commit, which moves
Codex's trust-dialog + approval builders onto ConditionModules driven by the
shared evaluator. Verified byte-for-byte against the prior path.

NOTE: the codex-headless submodule PR (Juliusolsson05/codex-headless#17) must
merge before this gitlink resolves on integration.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… bump — PR-2 review fixes

Completes the agent-code side of the PR-2 adversarial-review fixes (the
prior fix agent pushed codex-headless#17 but stalled before delivering
this). Reconstructs app-side evaluator.ts to match the reviewed vendored
copy (codex-headless@9480f580): makeEvaluator now defensively copies its
module list (`const registry = [...modules]`) so external mutation of an
exported CODEX_MODULES cannot reorder/alter a running evaluator. Bumps
the codex-headless gitlink to 9480f580 (fresh per-call action objects +
softened "golden matrix" comment wording). Verified zero drift: the sync
script regenerates the vendored copy byte-identically.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@Juliusolsson05
Juliusolsson05 merged commit 0f8dcc5 into integration/conditions-framework Jun 22, 2026
@Juliusolsson05
Juliusolsson05 deleted the feat/conditions-codex-migration branch June 22, 2026 08:30
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