Skip to content

Preserve nominal ID kinds through input, turn and storage owners - #2943

Merged
trissim merged 47 commits into
mainfrom
refactor/id128-nominal-type
Oct 24, 2026
Merged

trissim merged 47 commits into
mainfrom
refactor/id128-nominal-type

Conversation

@trissim

@trissim trissim commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

Core ids lost their kind when decoded as plain str. Introducing nominal values exposed the other half of that defect: string-typed SQL readers and exact-str guards rejected ids that the producer had already minted correctly. This batch retains the kind through the producing fields, ingress, storage readers, turn state and native-send owners.

Current head: a953c92. Remote equals HEAD. No main merge or enqueue requested.

Final decoder declaration

AssignmentResponse.from_columns is the actual nullable SQL entry: it decodes the three columns, returns UnansweredResponse when all are NULL, constructs AnsweredResponse when complete, and refuses a partial row. notification_assignment.py now declares it with @boundary_decoder directly under @classmethod. Only that import and decorator changed in the final commit; the decode and turn_input_source.py are untouched.

The commander’s review fork reported 1052 passed on parent 07e83c6 across 88 affected files, with only the two failures also present on main. All 28 earlier branch failures passed there. This unit ran no tests.

Requested local comparison on the current committed head, using two workers:

PYTHONPATH=src:tools python tools/debt_ratchet.py --root src --base origin/main --head HEAD --workers 2
{
  "introduced_lines": {
    "NoneAbsence:src/agent_comms/turn_input_source.py": [
      48,
      90
    ]
  }
}

Its only positive delta is the explicitly approved NoneAbsence:src/agent_comms/turn_input_source.py +1. Exit 1 is solely that approved result. No baseline, exemption, pragma or decoder rewrite was added.

Value and storage ownership

NominalText inherits its spelled_by declaration. NominalSpelling records the spelling once; the declared decoder constructs the field's own kind and refuses sibling kinds with MalformedRecord after checking spelling. WireValue and NominalText answer representation() themselves. Id128 owns the UUID mint; ClaimId, AssignmentGeneration, TurnId, GoalId, RootId, ThreadLookup and InputId inherit it without per-kind spellings or mint copies. MessageId owns the unchanged twelve-hex digest. The test pins three old-main hashes and the existing wire strings. The temporary plain-str allowance names RedecodesDeletedPhase.

Mints move from Id128Text/MessageIdText to values, including default_factory. CompactionPhase owns the compaction fact; its id has no compaction prefix. The old thread_identity.TurnId wrapper is RegisteredTurn here; its complete removal and recorded-context/task carry are in separate draft #2965.

The duplicate agent_backend.InputId is deleted. Input producers declare InputId and consumers no longer box/unwrap it. MessageReference and message publication/response fields declare MessageId. TypedRow readers decode those declarations once at SQL entry. TextStorage supports the nominal representation through its existing owner; producer digest includes the value declarations. These paired changes came from #2945/#2966, folded into this branch. Further PRs into this branch wait for the commander's review verdict.

Turn and send ownership

TurnState holds active: TurnDeclaration and finished: TurnRecord. NoObservedTurn/TurnFence own the runtime observation; the old optional observed id and finished_turn_id field are deleted. Required queued input ids come from PromptRequest's mint, with no second None/default mint.

TurnInputSource and OwnedTurn require TurnAcceptance. AcceptedTurn owns its queued acknowledgement, current-input check, start update and removal; NoTurnAcceptance leaves the queue alone. Controller | None is retained honestly, per the commander's explicit exception. Scheduled wakes, startup input and selected originals explicitly choose NoTurnAcceptance; queued inputs pass AcceptedTurn.

InputStartListener lives in agent_backend. ReportInputStart/from_callback and native_start callback arguments are deleted. SendBoundary is an ABC with send_original/send_following/send_interrupt; OwnedSend composes it with InputStartListener and CodingToolOwner. Permission before write and acknowledgement after commit have distinct names. TurnInputSource owns shared preparation once. The callable alias, interrupt_boundary field and lambdas are deleted. Direct native sessions preserve the existing unrestricted case through NoSendBoundary.

InputForwarding retains its original typed InputRequest in the one pending index; the RPC channel already tracks the wire command. Start matching returns zero or one InputId instead of a bool plus an optional id.

Latest read fixes: one commit, items 1–8

All eight ordered fixes and their affected fixture producers are in 2818c2d, directly on reviewed parent a1f4ffb.

Item Source
1 input_disposition.py:268: in_turn takes TurnId; its three production callers already pass lease.turn_id.
2 test_input_documents.py:30: one TURN; all fourteen matching writer literals and reads at :135/:210/:444 use it. Deliberately different turns and raw malformed-wire fixtures remain distinct.
3 test_goal_resume.py:87/:88/:109: one turn value for begin_turn and reports_turn. Report-turn signatures and empty report state are untouched.
4 coordination_tables/assignments.py:147: WakeAssignment.message_id is MessageId. Its existing FrozenColumn CHECK is AST-identical.
5 selected_turn.py:154 passes assignment.message_id directly; the second decode and unused imports are deleted. selected_write_plan's external-input decode is untouched.
6 notification_assignment.py:78 decodes MessageId
7 historical_native_inputs.py:44/:94/:97: historical source_message_id, SQL input_id and SQL message_id declare their nominal kinds; TypedRow supplies decoding.
8 test_native_channel_reply_roundtrip.py:394 uses state.finished != NoTurnRecord(), alongside the existing active-state check. The invented Thread.claimed_generation access is deleted.

The constructor inventory found six WakeAssignment fixtures still passing label strings; test_coordination, test_coordination_store and test_coordination_nominal now mint MessageId at those constructors. historical_source in test_native_context_reference also mints its source message id. These are producer changes, with no store-value wrapping or consumer re-decode.

Readers inspected and left unchanged

Reader Why
coordination_tables/assignments.py:84/:127/:135/:186 References, binding and validation now receive the kind from the row declaration.
selected_source_batch.py:35 Its store read and committed-source check use that same declaration.
selected_participant.py:301 MessageReference receives the decoded assignment id.
optional_awareness_projection.py:182/:209/:257 Existing comparisons and rendered candidates/provenance retain the decoded id.
context_segments/optional_awareness.py:76/:84 Candidate and context values derive from the assignment.
channel_coding_tools.py:122 WakeClaim receives the decoded assignment id directly.
cohort_schema.py:175 Existing acknowledgement equality remains valid; no extra decode is needed.
private_send_rules.py:292 Current/captured assignment ids are compared directly.
native_prompt_binding.py:185 The existing SQL parameter accepts the decoded text value.
context_segments/wake.py:115 The answer's MessageReference now receives MessageId from AssignmentResponse.from_columns.
historical_native_inputs.py:127/:146/:196 TypedRow reads the nominal fields; the query and proof constructor pass those values directly.
coordination_cohort.py:136 The assignment producer already supplies Message.message_id.
transcript_routes.py:172, messaging.py:111, coordination_response.py:386 in_turn callers already supply lease.turn_id.

AST inventory: 961 production/test/tool modules, zero parse omissions; immutable skill fixtures excluded. Known constructors and the relevant attribute consumers were read semantically. External callers and ambiguous dynamic invocation are outside this inventory.

Source checks and lint

B, C and D from the order print nothing. A's literal command still prints two legitimate pre-existing lease checks:

tests/test_input_documents.py:176:    claim_scope = lease.claimed_generation
tests/test_input_documents.py:231:        replace(lease, claimed_generation=claim_scope + 1), "a" * 32,

The missing stored-turn lookups, reports_turn("same-assistant-turn") and owner.claimed_generation are gone. The two real lease checks are preserved rather than hidden to make an overbroad grep green; the commander has this exact output.

ruff check: 157 Python files; 1268 existing findings;
0 new per-file/rule findings; 0 changed-line findings

Only changed lines/import blocks fixed. No formatter, tests, suite or ratchet run. The five changed source modules import successfully. The review fork runs the affected paths.

Stored claim carry remains separately owned

WakeClaim is version 2 because its nested RecordedScope.turn_id schema became stricter. RecordedScope itself has no version. The existing carry family contains WakeClaimTurnIds, intended to validate v1 ids and rewrite the v2 record; no schema override, substitute id or silent skip is used. The commander owns the remaining carry-walk failure. This latest commit touches neither the tool, its fixture nor field_codec.py/typed_table.py. The reviewed fixture-path follow-up #2967 is already in parent a1f4ffb.

Assumed: nominal values keep their existing wire spellings; raw CLI/tool/SQL data decodes at its actual entry; nullable published-response columns remain the existing external unit; real lease-generation checks are not the deleted invented Thread attribute; tests stay with the review fork. No current-head test pass or live-install qualification is claimed.

git diff --stat origin/main...HEAD
 plans/ID128-NOMINAL-PLAN.md                        |  34 +-
 src/agent_comms/acp_extension.py                   |  13 +-
 src/agent_comms/agent_activity.py                  |   3 +-
 src/agent_comms/agent_backend.py                   |  44 +-
 src/agent_comms/backend.py                         |  80 +-
 src/agent_comms/bus.py                             |   6 +-
 src/agent_comms/commander/judge.py                 |   8 +-
 src/agent_comms/commander/order.py                 |   3 +-
 src/agent_comms/coordination_response.py           |   3 +-
 src/agent_comms/coordination_tables/assignments.py |   4 +-
 .../coordination_tables/publications.py            |   6 +-
 src/agent_comms/coordination_tables/responses.py   |   4 +-
 src/agent_comms/cursor_owner.py                    |   2 +-
 src/agent_comms/cursor_publication.py              |   6 +-
 src/agent_comms/envelope_claim_transitions.py      |  11 +-
 src/agent_comms/field_codec.py                     | 854 +++++++++++++--------
 src/agent_comms/following_inputs.py                |  13 +-
 src/agent_comms/goal_turn_permit.py                |   4 +-
 src/agent_comms/goals.py                           |   8 +-
 src/agent_comms/historical_native_inputs.py        |  10 +-
 src/agent_comms/input_attempt.py                   |   6 +-
 src/agent_comms/input_disposition.py               |   8 +-
 src/agent_comms/input_drain.py                     |  17 +-
 src/agent_comms/installer/launch.py                |   4 +-
 src/agent_comms/installer/probes/carry.py          |  21 +
 src/agent_comms/maintenance_barrier.py             |   9 +-
 src/agent_comms/manual_compaction_bridge.py        |   4 +-
 src/agent_comms/message_reference.py               |   4 +-
 src/agent_comms/messages.py                        |   6 +-
 src/agent_comms/native_input_record.py             |  27 +-
 src/agent_comms/native_prompt_binding.py           |  14 +-
 src/agent_comms/native_runtime_input.py            |   6 +-
 src/agent_comms/native_turn_context.py             |   4 +-
 src/agent_comms/nk_foreground.py                   |   6 +-
 src/agent_comms/notification_assignment.py         |   5 +-
 src/agent_comms/obligation_states.py               |  12 +-
 src/agent_comms/ordinary_turn_rules.py             |   4 +-
 src/agent_comms/owned_send.py                      | 151 ++--
 src/agent_comms/owned_turn.py                      |  34 +-
 src/agent_comms/pi_native_backend.py               |   3 +-
 src/agent_comms/plan_phases.py                     |  90 ++-
 src/agent_comms/private_registry_guard.py          |   4 +-
 src/agent_comms/private_send_claim.py              |   3 +-
 src/agent_comms/private_send_stage.py              |  20 +-
 src/agent_comms/proven_source_coverage.py          |   3 +-
 src/agent_comms/publisher.py                       |   4 +-
 src/agent_comms/queued_input.py                    |  27 +-
 src/agent_comms/registration.py                    |  17 +-
 src/agent_comms/registry_store.py                  |   4 +-
 src/agent_comms/schedule_rules.py                  |   2 +
 src/agent_comms/selected_actions.py                |   4 +-
 src/agent_comms/selected_participant.py            |   4 +-
 src/agent_comms/selected_turn.py                   |   4 +-
 src/agent_comms/selected_write_plan.py             |   8 +-
 src/agent_comms/send_outcome.py                    |  45 +-
 src/agent_comms/started_input.py                   |  15 +-
 src/agent_comms/task_sources.py                    |   6 +-
 src/agent_comms/thread_identity.py                 |  35 +-
 src/agent_comms/threads.py                         |  18 +-
 src/agent_comms/todos.py                           |  12 +-
 src/agent_comms/tracked_turn.py                    |  18 +-
 src/agent_comms/turn_context.py                    |   4 +-
 src/agent_comms/turn_goal_account.py               |   6 +-
 src/agent_comms/turn_input_source.py               | 172 ++++-
 src/agent_comms/turn_inputs.py                     |  69 +-
 src/agent_comms/turn_lease.py                      | 131 ++--
 src/agent_comms/turn_runner.py                     |  10 +-
 src/agent_comms/typed_table.py                     |  24 +-
 src/agent_comms/worker.py                          |   2 +
 tests/goal_stop_journey.py                         |  24 +-
 tests/native_backend_fixture.py                    |  54 +-
 tests/pi_session_turn.py                           |   2 +
 tests/test_absence_owners.py                       |  35 +-
 tests/test_acp.py                                  |   5 +-
 tests/test_acp_extension.py                        |  38 +-
 tests/test_acp_queue_contract.py                   |  15 +-
 tests/test_active_turn.py                          |  12 +-
 tests/test_activity_text.py                        |  14 +-
 tests/test_agent_backend.py                        |  10 +-
 tests/test_atomic_turn_source_owner.py             |  26 +-
 tests/test_backend.py                              |   8 +-
 tests/test_bus.py                                  |  38 +-
 tests/test_bus_files.py                            |  12 +-
 tests/test_bus_history_seq.py                      |   4 +-
 tests/test_carry_wake_claim_turn_ids.py            |  86 +++
 tests/test_channel_bus_ownership.py                |  12 +-
 tests/test_channels.py                             |   4 +-
 tests/test_claim_admission_verifier.py             |   7 +-
 tests/test_claim_bus_read.py                       |  23 +-
 tests/test_cli.py                                  |  13 +-
 tests/test_context_manifest_deltas.py              |  35 +-
 tests/test_context_source_inspection.py            |   8 +-
 tests/test_coordination.py                         |   8 +-
 tests/test_coordination_nominal.py                 |   8 +-
 tests/test_coordination_response.py                |   3 +-
 tests/test_coordination_store.py                   |   6 +-
 tests/test_current_input_origin.py                 |  77 +-
 tests/test_declarations.py                         |  32 +-
 tests/test_envelope_claim_transitions.py           |  19 +-
 tests/test_field_codec.py                          |  63 +-
 tests/test_goal_answers.py                         |   6 +-
 tests/test_goal_execution.py                       |   4 +-
 tests/test_goal_failure_observation.py             |   5 +-
 tests/test_goal_input_review.py                    |   6 +-
 tests/test_goal_reply_lock_scope.py                |  17 +-
 tests/test_goal_resume.py                          |   9 +-
 tests/test_goal_standby.py                         |  23 +-
 tests/test_goal_standby_liveness.py                |  41 +-
 tests/test_goal_wait_certified_reply.py            |   5 +-
 tests/test_historical_notifications.py             |  10 +-
 tests/test_history_view_declaration.py             |   3 +-
 tests/test_holder_standing_rules.py                |  18 +-
 tests/test_input_delivery_history.py               |  17 +-
 tests/test_input_disposition.py                    |  73 +-
 tests/test_input_documents.py                      | 206 +++--
 tests/test_install.py                              |   3 +-
 tests/test_interrupted_delivery_recovery.py        |   3 +-
 tests/test_l0_goal_input_closure.py                |  17 +-
 tests/test_live_drain_failure.py                   |   7 +-
 tests/test_maintenance_barrier.py                  |  18 +-
 tests/test_mcp_acceptance.py                       |  50 +-
 tests/test_message_notifications.py                |   7 +-
 tests/test_native_channel_reply_roundtrip.py       |   7 +-
 tests/test_native_context_reference.py             |  11 +-
 tests/test_native_retry_exhaustion_installed.py    |  12 +-
 tests/test_native_unknown_recovery.py              |   7 +-
 tests/test_observer_death.py                       |   3 +-
 tests/test_optional_awareness_projection.py        |   4 +-
 tests/test_ordinary_admission_rules.py             |   3 +-
 tests/test_owned_turn_goal_replacement.py          |  27 +-
 tests/test_preflight_feedback.py                   |  10 +-
 tests/test_private_idle_drain.py                   |  15 +-
 tests/test_project_resume.py                       |   8 +-
 tests/test_read_ledger.py                          |   4 +-
 tests/test_registration_authority.py               |  23 +-
 tests/test_registration_inheritance.py             |   5 +-
 tests/test_registration_process_identity.py        |   9 +-
 tests/test_registration_transitions.py             |  11 +-
 tests/test_reservation_rules.py                    |  19 +-
 tests/test_retained_context_operations.py          |   4 +-
 tests/test_roster_restoration.py                   |   3 +-
 tests/test_selected_tool_broker.py                 |   7 +-
 tests/test_stack_native_compaction.py              |   9 +-
 tests/test_stack_persistent_compaction.py          |  23 +-
 tests/test_task_decisions.py                       |  12 +-
 tests/test_thread_identity.py                      |  20 +-
 tests/test_tool_output.py                          |  24 +-
 tests/test_tracked_native_retry.py                 |  24 +-
 tests/test_turn_context_wire.py                    |  14 +-
 tests/test_turn_declaration.py                     |  31 +-
 tests/test_turn_goal_account.py                    |   5 +-
 tests/test_turn_identity_equality.py               |  11 +-
 tests/test_turn_inbox.py                           |   5 +-
 tests/test_turn_lease_release.py                   |  16 +-
 tests/test_turn_record.py                          |   7 +-
 tests/test_unscoped_claim_history.py               |  18 +-
 tests/verify_connector_tools.py                    |   6 +-
 tools/cutover/carry_wake_claim_turn_ids.py         |  65 ++
 158 files changed, 2577 insertions(+), 1376 deletions(-)

@trissim
trissim marked this pull request as ready for review October 23, 2026 22:32
Copilot AI balanced review requested due to automatic review settings October 23, 2026 22:32

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota.

An organization owner can choose the repository organization as the billing source for reviews requested by members with a paid Copilot license. Learn about Copilot code review billing.

@github-actions

github-actions Bot commented Oct 23, 2026 •

Copy link
Copy Markdown

debt-tools at 9eb631a, every finding:

grew NoneAbsence:tools/cutover/carry_wake_claim_turn_ids.py +1
grew StringSubscript +2
grew Unformatted:tools/cutover/carry_wake_claim_turn_ids.py +1
introduced NoneAbsence:tools/cutover/carry_wake_claim_turn_ids.py at lines [37]

@github-actions

github-actions Bot commented Oct 23, 2026 •

Copy link
Copy Markdown

debt-src at 82ccab9, every finding:

grew NoneAbsence:src/agent_comms/turn_input_source.py +1
introduced NoneAbsence:src/agent_comms/turn_input_source.py at lines [48, 90]

@trissim trissim changed the title Carry ID kinds as nominal text without changing stored strings Preserve nominal ID kinds through input, turn and storage owners Oct 24, 2026
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.

2 participants